/* eslint-disable no-mixed-spaces-and-tabs */
//变量在使用前必须声明
'use strict';

import * as Cesium from "cesium";

/**
 * 定义了所有的常数
 * @class
 * @description
 */
class Consts
{
    /**
     * 设置第一视角窗口的宽度
     */
    static UavViewerSize=500;
    /**
     * 最小角度差
     */
    static ZeroDegree=0.1;
    /**
     * 最短距离差
     */
    static ZeroDistance=0.01;
    /**
     * 标准的视锥高度，单位：米
     */
    static StandardFrustumSize=50;
    /**
     * 最大的俯仰角，超过这个值，就太平了，与地面无交点
     */
    static MaxPitch=-3; 
    /**
     * 在ShowStatus时，如果超过这个时间（毫秒）没有收到数据，就表示飞机离线了
     */
    static OfflineThreshold=3000; 
    /**
     * 一个标准的球
     */
    static sphereGeometry = new Cesium.SphereGeometry({
        radius: 0.2,
        slices: 4, // 纬度方向的分段数
        stacks: 4, // 经度方向的分段数
        vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
    });
    /**
     * Cesium视窗对应的高度偏移值，也就是底面的椭球高，这样，当没有3dtiles，地面上的物体就会剪掉这个高度，降到地面
     */
    static ViewerHightOffset=new Map();
}

/**
 * 定义了所有的通用方法
 * @class
 * @description
 */
export class Tools
{
    static DownloadToFile(content, fileName)
    {        
        // 创建一个Blob对象，包含文件内容
        var blob = new Blob([content], { type: 'text/plain' });

        // 创建一个URL对象，指向Blob对象
        var url = URL.createObjectURL(blob);

        // 创建一个<a>元素
        var link = document.createElement('a');

        // 设置href属性为Blob对象的URL
        link.href = url;

        // 设置download属性，指定下载时的文件名
        link.download = fileName; 

        // 触发点击事件
        document.body.appendChild(link);
        link.click();

        // 移除临时创建的<a>元素
        document.body.removeChild(link);

        // 释放Blob对象的URL
        URL.revokeObjectURL(url);
    }

    /**
     * 将对象格式化成json
     * @param {Object} data 
     * @returns {string}
     */
    static ToJson(data)
    {
        // 将对象序列化为JSON字符串
        return JSON.stringify(data, null, 4); // 使用4个空格进行缩进，使输出更易读
    }
    /**
     * 加载3dTiles，并飞过去看
     * @param {Cesium.Viewer} viewer 
     * @param {String} dataPath 
     * @param {boolean} flyTo 是否需要飞过去
     */
    static async Load3DTiles(viewer, dataPath, flyTo=true)
    {
        const tileset = await Cesium.Cesium3DTileset.fromUrl(dataPath, {
            cacheBytes: 512*1024*1024*10,
            maximumScreenSpaceError: 8,
            // skipLevelOfDetail: true,
            // baseScreenSpaceError: 1024,
            // skipScreenSpaceErrorFactor: 16,
            // skipLevels: 1,
            // immediatelyLoadDesiredLevelOfDetail: false,
            // loadSiblings: false,
            // cullWithChildrenBounds: true
        });
        var modelEntity1 = viewer.scene.primitives.add(tileset);
        if (flyTo)
        {
            var boundingSphere1 = modelEntity1.boundingSphere;
            var r=boundingSphere1.radius;
            viewer.camera.flyTo({
                destination: Tools.MoveOnEarth(
                    boundingSphere1.center, -r, -r, r),
                orientation: {
                    heading: Cesium.Math.toRadians(45.0), 
                    pitch: Cesium.Math.toRadians(-45.0), 
                    roll: 0.0 
                },
            });
        }
        if (Consts.ViewerHightOffset.has(viewer))
        {
            Consts.ViewerHightOffset.set(viewer,0);
        }
    }

    /**
     * 初始化cesium视窗
     * @param {String} container 放cesium视窗的div ID
     * @param {number} groundWgs84Hight 地面的椭球高
     * @returns Cesium.Viewer
     */
    static BuildViewer(container, groundWgs84Hight=0)
    {         
        
        var viewer = new Cesium.Viewer(container, {
                geocoder: false,				//隐藏查找控件
                homeButton: false,				//隐藏视角返回初始位置按钮
                sceneModePicker: false,			//隐藏视角模式3D 2D CV
                baseLayerPicker: false,			//隐藏图层选择
                navigationHelpButton: false,	//隐藏帮助
                animation: false,				//隐藏动画控件
                timeline: false,				//隐藏时间线控件
                fullscreenButton: false,		//隐藏全屏
                scene3DOnly: true, // 确保场景是3D模式
                infoBox: false, // 禁用默认信息框
                selectionIndicator: false

        });   
        //绿色视野框禁用  
        viewer.scene.camera.frustumDebug = false;
        // 移除当前的底图图层
        viewer.imageryLayers.removeAll();
        //隐藏ion
        viewer._cesiumWidget._creditContainer.style.display = "none";

        //高德影像底图
        var gaode_image = new Cesium.UrlTemplateImageryProvider({
            url: 'https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
            maximumLevel: 18,
            minimumLevel: 3
        });

        viewer.imageryLayers.addImageryProvider(gaode_image);  

          
        //viewer.imageryLayers.addImageryProvider(gaode_image_label);

        // // 添加方向光
        // const directionalLight = new Cesium.DirectionalLight({
        //     direction: Cesium.Cartesian3.normalize(new Cesium.Cartesian3(1.0, 1.0, -1.0), new Cesium.Cartesian3()), // 光的方向
        //     color: Cesium.Color.WHITE, // 光的颜色
        //     intensity: 1.0 // 光的强度
        // });
        
        // // 启用光照效果
        // viewer.scene.globe.enableLighting = true;
        // viewer.scene.light = directionalLight;

        Consts.ViewerHightOffset.set(viewer,groundWgs84Hight);

        return viewer;
    }

    /**
     * 根据椭球高调点高度
     * @param {Cesium.Viewer} viewer 
     * @param {Cesium.Cartesian3[]} cartesians 
     * @returns {Cesium.Cartesian3[]}
     */
    static AdjustManyHeight(viewer, cartesians)
    {
        if (!Consts.ViewerHightOffset.has(viewer)) return cartesians;
        if (Consts.ViewerHightOffset.get(viewer)==0) return cartesians;
        var result=[];
        for(var i=0;i<cartesians.length;i++)
        {
            var cartographic = Cesium.Cartographic.fromCartesian(cartesians[i]);
            cartographic.height=cartographic.height-Consts.ViewerHightOffset.get(viewer);
            result.push(Cesium.Ellipsoid.WGS84.cartographicToCartesian(cartographic));
        }
        return result;
    }

    /**
     * 根据椭球高调点高度
     * @param {Cesium.Viewer} viewer 
     * @param {Cesium.Cartesian3} cartesian 
     * @returns {Cesium.Cartesian3}
     */
    static AdjustHeight(viewer, cartesian)
    {
        if (!Consts.ViewerHightOffset.has(viewer)) return cartesian;
        if (Consts.ViewerHightOffset.get(viewer) == 0) return cartesian;
        
        var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
        cartographic.height=cartographic.height-Consts.ViewerHightOffset.get(viewer);
        return Cesium.Ellipsoid.WGS84.cartographicToCartesian(cartographic)
    }

    /**
     * 插值从点cFrom到cTo，按照比例来，partical为0，就是cFrom，patrial为1，就是cTo
     * @param {Cesium.Cartesian3} cFrom 
     * @param {Cesium.Cartesian3} cTo 
     * @param {number} partial 0~1
     * @returns {Cesium.Cartesian3}
     */
    static InterpolateCartesian3(cFrom, cTo, partial)
    {
        if (cFrom==null) return null;
        if (cTo==null) return null;
        var x=Tools.InterpolateNumber(cFrom.x, cTo.x, partial);
        var y=Tools.InterpolateNumber(cFrom.y, cTo.y, partial);
        var z=Tools.InterpolateNumber(cFrom.z, cTo.z, partial);   
        return new Cesium.Cartesian3(x,y,z);     
    }

    /**
     * 插值数组从点cFrom数组到cTo数组，按照比例来，partical为0，就是cFrom，patrial为1，就是cTo
     * @param {Cesium.Cartesian3[]} cFrom 
     * @param {Cesium.Cartesian3[]} cTo 
     * @param {number} partial 0~1
     * @returns {Cesium.Cartesian3[]}
     */
    static InterpolateCartesian3Array(cFrom, cTo, partial)
    {
        var result=[];
        if (cFrom.length!=cTo.length) return result;
        for(let i=0;i<cFrom.length; i++)
        {
            result.push(Tools.InterpolateCartesian3(cFrom[i], cTo[i], partial));
        }
        return result;
    }

    /**
     * 插值从数字nFrom到nTo，partical为0，就是nFrom，patrial为1，就是nTo
     * @param {number} nFrom 
     * @param {number} nTo 
     * @param {number} partial 0~1
     * @returns {number}
     */
    static InterpolateNumber(nFrom, nTo, partial)
    {
        return nFrom+(nTo-nFrom)*partial;
    }

    /**
     * 判断两个点是不是同一个点
     * @param {Cesium.Cartesian3} c1 
     * @param {Cesium.Cartesian3} c2 
     * @returns {boolean}
     */
    static SameCartesian3(c1, c2)
    {
        if (c1.x!=c2.x) return false;
        if (c1.y!=c2.y) return false;
        if (c1.z!=c2.z) return false;
        return true;
    }

    /**
     * 计算一个位置在地球表面偏移后的位置
     * @param {Cesium.Cartesian3} cartesian 
     * @param {number} delta_x 往东多少米
     * @param {number} delta_y 往北多少米
     * @param {number} delta_z 往上多少米
     * @returns {Cesium.Cartesian3}
     */
    static MoveOnEarth(cartesian, delta_x=0, delta_y=0, delta_z=0)
    {
        //建立以cartesian为原点，X轴为east,Y轴为north,Z轴朝上的坐标系
        const localFrame = Cesium.Transforms.eastNorthUpToFixedFrame(cartesian);
        const offsetLocalLocation=new Cesium.Cartesian3(delta_x,delta_y,delta_z);
        return Cesium.Matrix4.multiplyByPoint(localFrame, offsetLocalLocation, new Cesium.Cartesian3()); 
    }

    /**
     * 计算从pointA到pointB的航向角，就是两点在水平方向上形成的向量与正北的夹角
     * @param {Cesium.Cartesian3} pointA 
     * @param {Cesium.Cartesian3} pointB 
     * @returns {number} 角度 0~360
     */
    static HeadingPointToPoint(pointA, pointB){
        if (pointA==null) return 0;
        if (Tools.SameCartesian3(pointA,pointB)) return 0;
        //建立以点A为原点，X轴为east,Y轴为north,Z轴朝上的坐标系
        const transform = Cesium.Transforms.eastNorthUpToFixedFrame(pointA);
        //向量AB
        const positionvector = Cesium.Cartesian3.subtract(pointB, pointA, new Cesium.Cartesian3());
        //因transform是将A为原点的eastNorthUp坐标系中的点转换到世界坐标系的矩阵
        //AB为世界坐标中的向量
        //因此将AB向量转换为A原点坐标系中的向量，需乘以transform的逆矩阵。
        const vector = Cesium.Matrix4.multiplyByPointAsVector(Cesium.Matrix4.inverse(transform, new Cesium.Matrix4()), positionvector, new Cesium.Cartesian3());
        //归一化
        const direction = Cesium.Cartesian3.normalize(vector, new Cesium.Cartesian3());
        //heading
        const heading = Math.atan2(direction.y, direction.x) - Cesium.Math.PI_OVER_TWO;
        return Cesium.Math.toDegrees(Cesium.Math.TWO_PI - Cesium.Math.zeroToTwoPi(heading));
    }

    /**
     * 基于方向角和位置，计算方向四元数
     * @param {number} heading - 角度
     * @param {Cesium.Cartesian3} position - 目标位置
     * @returns {Cesium.Quaternion}
     */
    static GetOrientationBasedHeading(heading, position){
        const hpr = new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(heading), 0, 0);
        var orientation = Cesium.Transforms.headingPitchRollQuaternion(position,hpr);
        return orientation;
    }

    /**
     * 经纬度和高度，转成Cesium.Cartesian3
     * @param {number} longitude 角度
     * @param {number} latitude 角度
     * @param {number} height 米
     * @returns {Cesium.Cartesian3}
     */
    static GetCartesian3FromDegreesHeight(longitude, latitude, height)
    {
        let lnglat=Cesium.Cartographic.fromDegrees(longitude,latitude);
        var cartesian3=Cesium.Cartesian3.fromRadians(lnglat.longitude,lnglat.latitude,height);  
        return cartesian3;
    }

    /**
     * 从原点延指定观察方向和长度，获得的一个点位
     * @param {Cesium.Cartesian3} origin - 原点位置.
     * @param {ViewDegree} viewDegree - 观察方向.
     * @param {number} distance - 长度，米.
     * @returns {Cesium.Cartesian3} 计算得到的点位.
     */
    static GetPositionAlongViewDegree(origin, viewDegree, distance)
    {
        // 定义参考点（局部平面坐标系的原点）
        const referencePoint = origin;
        // 创建局部平面坐标系
        const localFrame = Cesium.Transforms.eastNorthUpToFixedFrame(referencePoint);
        // 定义方位角、俯仰角和距离
        var azimuth = Cesium.Math.toRadians(viewDegree.azimuth ? viewDegree.azimuth:0); 
        var elevation = Cesium.Math.toRadians(viewDegree.pitch ? viewDegree.pitch:0); 
        const localPoint=new Cesium.Cartesian3(
            distance * Math.cos(elevation) * Math.sin(azimuth), distance * Math.cos(elevation) * Math.cos(azimuth), distance * Math.sin(elevation))
        const globalPoint =Cesium.Matrix4.multiplyByPoint(localFrame, localPoint, new Cesium.Cartesian3());
        return globalPoint;
    }

    /**
     * 从origin出发，延vector，长度为distance的点
     * @param {Cesium.Cartesian3} origin 
     * @param {Cesium.Cartesian3} vector -这需要是一个归一化的向量！！
     * @param {number} distance 米
     * @returns {Cesium.Cartesian3}
     */
    static GetPositionAlongVector(origin, vector, distance)
    {
        return Cesium.Cartesian3.add(origin, 
            Cesium.Cartesian3.multiplyByScalar(vector, distance, new Cesium.Cartesian3()),
            new Cesium.Cartesian3());
    }

    /**
     * 根据水平视野角获得垂直视野角
     * @param {number} hfov - 水平视场角，角度.
     * @param {number} aspect - 宽/高.
     * @returns {number} 垂直视场角，角度.
     */
    static GetVerticalFov(hfov, aspect)
    {
        const half_hfov=Cesium.Math.toRadians(hfov/2);
        const half_width=Math.tan(half_hfov)*1;          //假设相机距离地面为1, 视锥平面的半宽
        const half_height=half_width/aspect;             //视锥面的半高
        return Cesium.Math.toDegrees(Math.atan(half_height/1))*2;   
    }

    /**
     * 计算一个点绕指定旋转中心旋转
     * @param {Cesium.Matrix3} matrix - 旋转矩阵.
     * @param {Cesium.Cartesian3} center - 中心点位置.
     * @param {Cesium.Cartesian3} point - 待旋转点位置.
     * @returns {Cesium.Cartesian3} 旋转后的位置.
     */
    static RotatePointByMatrix(matrix, center, point)
    {
        // 计算旋转点相对于中心点的向量
        const r = Cesium.Cartesian3.subtract(point, center, new Cesium.Cartesian3());
        // 应用旋转矩阵
        const rPrime = Cesium.Matrix3.multiplyByVector(matrix, r, new Cesium.Cartesian3());
        // 计算旋转后点的新坐标
        const result = Cesium.Cartesian3.add(center, rPrime, new Cesium.Cartesian3());
        return result;
    }

    /**
     * 把Cesium.Cartesian3转成经纬度和高度打印出来，用于debug
     * @param {Cesium.Cartesian3} cartesian 
     * @returns {string} 打印的经纬度和高度.
     */
    static PrintLocation(cartesian)
    {
        // 将 Cartesian3 转换为 Cartographic
        const cartographic = Cesium.Cartographic.fromCartesian(cartesian);

        // 提取经纬度和高度
        const longitude = Cesium.Math.toDegrees(cartographic.longitude); // 经度（单位：度）
        const latitude = Cesium.Math.toDegrees(cartographic.latitude);   // 纬度（单位：度）
        const height = cartographic.height; 
        console.log(`经度: ${longitude.toFixed(6)}°`);
        console.log(`纬度: ${latitude.toFixed(6)}°`);
        console.log(`高度: ${height.toFixed(2)} 米`);  
        return longitude.toFixed(6)+", "+latitude.toFixed(6)+", "+height.toFixed(2);
    }    

    /**
     * 经过两个三维坐标点的直线与z=zPlane平面的交点
     * @param {Cesium.Cartesian3} point1 
     * @param {Cesium.Cartesian3} point2 
     * @param {number} zPlane 
     * @returns {Cesium.Cartesian3}
     */
    static LineIntersectionWithZPlane(point1, point2, zPlane) {
        // 计算参数 t
        const t = (zPlane - point1.z) / (point2.z - point1.z);

        // 使用参数 t 计算交点的 x 和 y 坐标
        const x = point1.x + t * (point2.x - point1.x);
        const y = point1.y + t * (point2.y - point1.y);

        // 交点的 z 坐标为 zPlane
        const z = zPlane;

        return new Cesium.Cartesian3(x, y, z);
    }

    /**
     * 计算一条射线与地平面的交点
     * @param {Cesium.Cartesian3} pFrom
     * @param {Cesium.Cartesian3} pTo
     * @returns {Cesium.Cartesian3} 交点，如果没相交，则为null.
     */    
    static PickOnEarth(pFrom, pTo)
    {
        // 将 Cartesian3 转换为 Cartographic
        const cFrom = Cesium.Cartographic.fromCartesian(pFrom);
        const origin=Cesium.Cartesian3.fromRadians(cFrom.longitude, cFrom.latitude, 0);
        // 创建局部平面坐标系
        const localFrame = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
        // 将地理坐标转换为局部平面坐标
        const tFrom =Cesium.Matrix4.multiplyByPoint(
            Cesium.Matrix4.inverse(localFrame, new Cesium.Matrix4()), pFrom, new Cesium.Cartesian3());

        const tTo =Cesium.Matrix4.multiplyByPoint(
            Cesium.Matrix4.inverse(localFrame, new Cesium.Matrix4()), pTo, new Cesium.Cartesian3());

        if (tTo.z>=tFrom.z) return null;

        var result=Tools.LineIntersectionWithZPlane(tFrom, tTo, 0);

        var pitch=Cesium.Math.toDegrees(Math.atan2(tFrom.z, Cesium.Cartesian3.magnitude(result)));

        if (-pitch<Consts.MaxPitch)
        {
            result=Cesium.Matrix4.multiplyByPoint(localFrame, result, new Cesium.Cartesian3());        
            return result;
        }
        else
        {
            return null;
        }
    }

    /**
     * 删除所有实体
     * @param {Cesium.Entity[]} entities - 实体数组.
     * @param {Cesium.Viewer} viewer - Cesium视窗
     */   
    static RemoveAllEntities(entities, viewer)
    {
        if (!Cesium.defined(viewer)) return;
        for(let i=0;i<entities.length;i++)
        {
            viewer.entities.remove(entities[i]);
        }
        entities.length=0;
    }

    /**
     * 建立一个点实体
     * @param {Cesium.Viewer} viewer 
     * @param {Cesium.Cartesian3} _position 
     * @param {String} _label      
     * @param {Cesium.Color} _pointColor, 如Cesium.Color.RED
     * @returns {Cesium.Entity}
     */
    static BuildPointEntity(viewer, _position, _label="", 
        _pointColor=Cesium.Color.RED, _pointSize=10)
    {
        var entity=viewer.entities.add({
            position: _position,
            point: {
                color: _pointColor,
                pixelSize: _pointSize
            },
            label: {
                text: _label, 
                font: "20px Helvetica",
                fillColor: Cesium.Color.YELLOW,
                outlineColor: Cesium.Color.BLACK,
                outlineWidth: 1,
                style: Cesium.LabelStyle.FILL_AND_OUTLINE,
                pixelOffset:new Cesium.Cartesian2(0,-15)            //偏移
            }, 
        });
        return entity;
    }

    /**
     * 建立一个线实体
     * @param {Cesium.Viewer} viewer 
     * @param {Cesium.Cartesian3[]} _ps 
     * @param {Cesium.Color} _lineColor 
     * @param {number} _width 
     * @returns {Cesium.Entity}
     */
    static BuildLineEntity(viewer, _ps, _lineColor=Cesium.Color.RED, _width=1)
    {
        return Tools.BuildLineEntityWithMaterial(viewer, _ps, new Cesium.ColorMaterialProperty(_lineColor), _width);
    }

    /**
     * 建立一个线实体按材质来
     * @param {Cesium.Viewer} viewer 
     * @param {Cesium.Cartesian3[]} _ps 
     * @param {Cesium.Material} _material 
     * @param {number} _width 
     * @returns {Cesium.Entity}
     */
    static BuildLineEntityWithMaterial(viewer, _ps, _material, _width=1)
    {
        let entity=viewer.entities.add({
            polyline: {
                positions:_ps,
                material: _material,
                width: _width,
                clampToGround:false,
            }}); 
        return entity;
    }

    /**
     * 
     * @param {Cesium.Viewer} viewer 
     * @param {Cesium.Cartesian3[]} _ps 
     * @param {Cesium.Color} _polygonColor 
     * @returns {Cesium.Entity}
     */
    static BuildPolygonEntity(viewer, _ps, _polygonColor=Cesium.Color.YELLOW.withAlpha(0.3), _outlineColor=null)
    {
        if (_outlineColor==null) _outlineColor=_polygonColor;
        let entity=viewer.entities.add({
            polygon: {
                hierarchy: _ps,
                material: new Cesium.ColorMaterialProperty(_polygonColor),
                clampToGround:false,
                perPositionHeight: true,
                outline: true,
                outlineColor: _outlineColor
            }});                
        return entity;
    }
}

/**
 * 定义一个包含方位角和俯仰角的观察方向
 * 其中的所有参数和属性成员都是角度为单位，
 * 其中俯仰角水平为0，向下为负值，向上为正值，(-90 ~ 90) 
 * 方位角朝北为0，顺时针为正值 (0, 360)
 */
export class ViewDegree
{
    /**
     * 定义一个观察方向
     * @param {numbernumber} _azimuth - 方位角朝北为0，顺时针为正值，角度(0, 360).
     * @param {numbernumber} _pitch - 俯仰角水平为0，向下为负值，向上为正值(-90 ~ 90).
     */
    constructor(_azimuth, _pitch)
    {
        while(_azimuth<0)
        {
            _azimuth += 360;
        }
        while(_azimuth>=360)
        {
            _azimuth -= 360;
        }
        while(_pitch<-180)
        {
            _pitch += 360;
        }
        while(_pitch>=180)
        {
            _pitch -= 360;
        }

        if (_pitch<-90)
        {
            _pitch=-(180+_pitch);
        }

        if (_pitch>90)
        {
            _pitch=180-_pitch;
        }

        this.azimuth=_azimuth;  
        this.pitch=_pitch;
    }

    /**
     * 插值后的ViewDegree
     * @param {ViewDegree} v1 
     * @param {ViewDegree} v2 
     * @param {number} factor - 所占比例
     * @returns {ViewDegree}
     */
    static Interpolate(v1, v2, factor)
    {
        return new ViewDegree(
            Tools.InterpolateNumber(v1.azimuth, v2.azimuth,factor),
            Tools.InterpolateNumber(v1.pitch, v2.pitch,factor));
    } 

    /**
     * 计算从相机到目标点的方位角和俯仰角
     * @param {Cesium.Cartesian3} observer - 相机位置.
     * @param {Cesium.Cartesian3} target - 目标点位置.
     * @param {number} headingWhenVertical - 当目标点处于正下方时，方向角用这个值，通常为无人机的方向角.
     * @returns {ViewDegree} 观察方向.
     */
    static GetViewDegree(observer, target, headingWhenVertical)
    {
        //// 定义观察点和目标点的地理坐标（经度、纬度、高度）
        //const observer = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 100); // 观察点
        //const target = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 200); // 目标点
        // 定义参考点（局部平面坐标系的原点）
        const referencePoint = observer;

        // 创建局部平面坐标系
        const localFrame = Cesium.Transforms.eastNorthUpToFixedFrame(referencePoint);
        // 将地理坐标转换为局部平面坐标
        const delta =Cesium.Matrix4.multiplyByPoint(Cesium.Matrix4.inverse(localFrame, new Cesium.Matrix4()), target, new Cesium.Cartesian3());
        // Cesium.Matrix4.multiplyByPoint(localFrame, target, new Cesium.Cartesian3());
        if (Math.abs(delta.x)<Consts.ZeroDistance) delta.x=0;
        if (Math.abs(delta.y)<Consts.ZeroDistance) delta.y=0;
        if (Math.abs(delta.z)<Consts.ZeroDistance) delta.z=0;
        let delta_xy=Math.sqrt(delta.x * delta.x + delta.y * delta.y)

        const pitch = Math.atan2(delta.z,delta_xy);   
        const pitchDegrees = Cesium.Math.toDegrees(pitch);
    
        const azimuth = Math.atan2(delta.x,delta.y);
        var azimuthDegrees = Cesium.Math.toDegrees(azimuth);
        //正射时，实际上朝向不好判断，就用无人机的方向
        if (Math.abs(pitchDegrees+90)<Consts.ZeroDegree) 
            azimuthDegrees=headingWhenVertical;

        return new ViewDegree(azimuthDegrees,pitchDegrees);
    }

}

export class ShowFrustum
{
    /**
     * 
     * @param {Cesium.Viewer} viewer 
     * @param {String} uavModeluri 
     * @param {number} modelOffsetDegree      
     * @param {String} uav_sn 
     * @param {CameraFrustum} frustum 
     */
    constructor(viewer, uavModeluri, modelOffsetDegree, uav_sn="", frustum=null)
    {        
        this.viewer=viewer;
        this.uavModeluri=uavModeluri;  
        this.modelOffsetDegree=modelOffsetDegree;          
        
        this.uav_sn=uav_sn;    

        this.AllEntities=[];
        this.uavEntity=null;
        this.sideWidth=1;
        this.sideColor=Cesium.Color.CHARTREUSE;
        this.sideMaterial=new Cesium.ColorMaterialProperty(this.sideColor);            
        this.faceMaterial=new Cesium.ColorMaterialProperty(Cesium.Color.YELLOW.withAlpha(0.2));
        this.rangeMaterial=new Cesium.ColorMaterialProperty(Cesium.Color.RED.withAlpha(0.1));
        this.UpdateFrustum(frustum);
    }

    /**
     * 切换视锥显示与否
     * @param {boolean} visible 
     */
    SetVisible(visible)
    {
        this.AllEntities.forEach((entity)=>{
            if (Cesium.defined(entity.name)) {
                if (entity.name.includes("face")||entity.name.includes("line"))
                {
                    entity.show=visible;
                }
            } 
        });
    }

    /**
     * 清空所有entities
     */
    RemoveAllEntities()
    {
        Tools.RemoveAllEntities(this.AllEntities, this.viewer);
        this.uavEntity=null;
    }

    BuildCallBackEntities()
    {
        if (this.uavModeluri!="")
        {
            //build UAV
            this.uavEntity=this.viewer.entities.add({    
                position: new Cesium.CallbackProperty((time, result)=>{
                    result = this.frustum.observer;     
                    //this.uavEntity.viewFrom = new Cesium.Cartesian3(0, 0, 20000);
                    return result;
                }, false),      
                //viewFrom: new Cesium.Cartesian3(0, 0, 5000), // 初始相机与实体之间的距离           
                orientation: new Cesium.CallbackProperty((time, result)=>{
                    // result = Tools.GetOrientationBasedHeading(this.frustum.uavHeading ? this.frustum.uavHeading : 0 + this.modelOffsetDegree ? this.modelOffsetDegree:0, this.frustum.observer);
                    if(!this.modelOffsetDegree){
                        this.modelOffsetDegree = 0;
                    }
                    result = Tools.GetOrientationBasedHeading(this.frustum.uavHeading ? this.frustum.uavHeading + this.modelOffsetDegree : this.modelOffsetDegree, this.frustum.observer);
                    return result;
                }, false),   
                model:new Cesium.ModelGraphics({
                    uri: this.uavModeluri,
                    minimumPixelSize: 64,
                    // colorBlendMode: Cesium.ColorBlendMode.MIX,
                    // color:Cesium.Color.WHITE,
                    // colorBlendAmount: 0.1
                    //silhouetteColor: Cesium.Color.GREEN,  //模型轮廓颜色
                    //silhouetteSize: 2.0,          //轮廓宽度
                    color:new Cesium.CallbackProperty((time, result)=>{
                        if (this.frustum.uavStatus!=2)
                        {                     
                            result= Cesium.Color.BLUE;
                        }
                        else
                        {
                            result= Cesium.Color.WHITE;
                        }
                        return result;
                    }, false), 
                    colorBlendMode:new Cesium.CallbackProperty((time, result)=>{
                        if (this.frustum.uavStatus!=2)
                        {                     
                            result= Cesium.ColorBlendMode.REPLACE; 
                        }
                        else
                        {
                            result= Cesium.ColorBlendMode.HIGHLIGHT; 
                        }
                        return result;
                    }, false), 
                }),
                label: {
                    text: this.uav_sn, 
                    font: "20px Helvetica",
                    fillColor: Cesium.Color.YELLOW,
                    outlineColor: Cesium.Color.BLACK,
                    outlineWidth: 1,
                    style: Cesium.LabelStyle.FILL_AND_OUTLINE,
                    pixelOffset:new Cesium.Cartesian2(0,-15)            //偏移
                }, 
            }); 
            this.AllEntities.push(this.uavEntity);
        }
        //建立地面范围
        var groundEntity=this.viewer.entities.add({
            polygon: {
                hierarchy: new Cesium.CallbackProperty((time, result)=>{
                    result=new Cesium.PolygonHierarchy(this.frustum.GetRange());
                    return result;
                }, false),
                material: this.rangeMaterial,
                clampToGround:false,                
                perPositionHeight: true,
            },
            name: 'ground'
            
        }); 

        this.AllEntities.push(groundEntity);

        //建立侧面
        var face_entity1=this.viewer.entities.add({
            polygon: {
                hierarchy: new Cesium.CallbackProperty((time, result)=>{
                    result=new Cesium.PolygonHierarchy(this.frustum.GetSideFace(0));
                    return result;
                }, false),
                material: this.faceMaterial,
                clampToGround:false,                       
                perPositionHeight: true,                
                outline: true,
                outlineColor: this.sideColor
            },
            name: 'face1'
        }); 
        this.AllEntities.push(face_entity1);

        var face_entity2=this.viewer.entities.add({
            polygon: {
                hierarchy: new Cesium.CallbackProperty((time, result)=>{
                    result=new Cesium.PolygonHierarchy(this.frustum.GetSideFace(1));
                    return result;
                }, false),
                material: this.faceMaterial,
                clampToGround:false,                       
                perPositionHeight: true,                
                outline: true,
                outlineColor: this.sideColor
            },
            name: 'face2'
        });    
        this.AllEntities.push(face_entity2);

        var face_entity3=this.viewer.entities.add({
            polygon: {
                hierarchy: new Cesium.CallbackProperty((time, result)=>{
                    result=new Cesium.PolygonHierarchy(this.frustum.GetSideFace(2));
                    return result;
                }, false),
                material: this.faceMaterial,
                clampToGround:false,                       
                perPositionHeight: true,                
                outline: true,
                outlineColor: this.sideColor
            },
            name: 'face3'
        }); 
        this.AllEntities.push(face_entity3);

        var face_entity4=this.viewer.entities.add({
            polygon: {
                hierarchy: new Cesium.CallbackProperty((time, result)=>{
                    result=new Cesium.PolygonHierarchy(this.frustum.GetSideFace(3));
                    return result;
                }, false),
                material: this.faceMaterial,
                clampToGround:false,                       
                perPositionHeight: true,                
                outline: true,
                outlineColor: this.sideColor
            },
            name: 'face4'
        });     
        this.AllEntities.push(face_entity4);
        
    }

    /**
     * 
     * @param {CameraFrustum} frustum 
     */
    UpdateFrustum(frustum)
    {
        this.UpdateTime = new Date(); 
        if (frustum==null)
        {
            this.frustum = frustum;
            return;
        } 
        if (this.frustum==null)
        {
            this.frustum=frustum;
            this.BuildCallBackEntities();
        }
        else
        {
            this.frustum=frustum;
        }
        if (this.uavEntity!=null&&this.uav_sn!="")
        {
            this.uavEntity.label.text=this.uav_sn+"[在线]";
            this.uavEntity.label.fillColor= Cesium.Color.YELLOW;
        }
    }

    /**
     * 检查上一次收到的视锥信息是否已经超时，如超时，就设为离线
     */
    CheckOffline()
    {
        var time=new Date();
        if ((time-this.UpdateTime)>Consts.OfflineThreshold)
        {
            if (this.uavEntity!=null&&this.uav_sn!="")
            {
                this.uavEntity.label.text=this.uav_sn+"[离线]";
                this.uavEntity.label.fillColor= Cesium.Color.RED;
            }
        }
    }
}

/**
 * 集成了关于视锥的所有运算
 * @class
 * @description
 */
export class CameraFrustum
{    
    /**
     * 初始化视锥体
     * @param {Cesium.Viewer} viewer - Cesium窗口.
     * @param {Cesium.Cartesian3} observer - 相机位置.
     * @param {Cesium.Cartesian3[]} surfaceCorners - 视锥底面数组，左上，右上，右下，左下.
     * @param {number} hfov - 水平视场角，角度.
     * @param {number} aspect - 宽/高.
     * @param {ViewDegree} viewDegree - 朝向，可以为null
     * @param {Cesium.Cartesian3} target - 目标点位置，可能为null.
     * @param {number} uavHeading - 角度.
     * @param {number} uavStatus -  - 0:未启动；1：未飞起来；2：在空中
     */
    constructor(viewer, observer, surfaceCorners, hfov, aspect, viewDegree, target, uavHeading, uavStatus)
    {        
        this.viewer=viewer;
        this.observer=observer;  
        this.surfaceCorners=surfaceCorners;
        this.hfov=hfov;
        this.aspect=aspect;   
        this.viewDegree=viewDegree;
        this.target=target; 
        this.uavHeading=uavHeading;
        this.uavStatus=uavStatus;
        
        this.vfov=Tools.GetVerticalFov(hfov,aspect);

        if (uavStatus==2)
        {
            this.surfaceWidth=Cesium.Cartesian3.distance(surfaceCorners[0], surfaceCorners[1]);
            this.surfaceHeight=Cesium.Cartesian3.distance(surfaceCorners[0], surfaceCorners[3]);    
            this.surfaceWidthUnitVector=Cesium.Cartesian3.normalize(
                Cesium.Cartesian3.subtract(surfaceCorners[1], surfaceCorners[0], new Cesium.Cartesian3()), new Cesium.Cartesian3());
            this.surfaceHeightUnitVector=Cesium.Cartesian3.normalize(
                Cesium.Cartesian3.subtract(surfaceCorners[3], surfaceCorners[0], new Cesium.Cartesian3()), new Cesium.Cartesian3());
            this.entities=[];
            this.groundCorners=this.GetGroundCorners();  //一定要放到最后，因为需要上面的参数才能计算
        }

        this.sideColor=Cesium.Color.CHARTREUSE;          
        this.faceColor=Cesium.Color.YELLOW.withAlpha(0.2);

    }
    
    /**
     * 
     * @param {number} time - 仅用于历史任务回放时，记录这个视锥的时刻
     */
    SetTime(time)
    {
        this.time=time;
    }

    /**
     * 生成一个插值的视锥
     * @param {CameraFrustum} f1 
     * @param {CameraFrustum} f2 
     * @param {number} time 
     * @returns {CameraFrustum}
     */
    static InterpolateByTime(f1,f2,time)
    {
        if (f1.uavStatus!=f2.uavStatus) return f1;
        if (f1.uavStatus!=2) return f1;

        var factor=(time-f1.time)/(f2.time-f1.time);

        var new_observer=Tools.InterpolateCartesian3(f1.observer, f2.observer, factor);
        var new_surfaceCorners= Tools.InterpolateCartesian3Array(f1.surfaceCorners, f2.surfaceCorners, factor)
        var new_hfov=Tools.InterpolateNumber(f1.hfov, f2.hfov, factor);
        var new_aspect=Tools.InterpolateNumber(f1.aspect, f2.aspect, factor);
        var new_viewDegree=ViewDegree.Interpolate(f1.viewDegree, f2.viewDegree, factor);        
        var new_target=Tools.InterpolateCartesian3(f1.target, f2.target, factor);
        var new_uavHeading=Tools.InterpolateNumber(f1.uavHeading, f2.uavHeading, factor);
        return new CameraFrustum(f1.viewer, new_observer, new_surfaceCorners, new_hfov, 
            new_aspect, new_viewDegree, new_target, new_uavHeading, 2);
    }

    /**
     * 获得视锥范围或地表范围
     * @returns {Cesium.Cartesian3[]}
     */
    GetRange()
    {
        return this.groundCorners==null?this.surfaceCorners:this.groundCorners;
    }

    /**
     * 仅获得视锥范围
     * @returns {Cesium.Cartesian3[]}
     */
    GetFrustumCorners()
    {
        return this.surfaceCorners;
    }

    /**
     * 获得视锥或地表指定序号的点坐标
     * @param {number} index 
     * @returns {Cesium.Cartesian3}
     */
    GetCorner(index)
    {
        return this.groundCorners==null?this.surfaceCorners[index]:this.groundCorners[index];
    }

    /**
     * 获得指定序号的视锥侧面三角形
     * @param {number} n 
     * @returns {Cesium.Cartesian3[]}
     */
    GetSideFace(n)
    {
        return [this.GetCorner(n),this.GetCorner((n+1)%4),this.observer];
    }

    /**
     * 获得指定序号的视锥侧面三角形的边线的节点坐标数组
     * @param {number} n 
     * @returns {Cesium.Cartesian3[]}
     */
    GetSideFaceOutline(n)
    {
        return [this.GetCorner(n),this.GetCorner((n+1)%4),this.observer,this.GetCorner(n)];
    }

    /**
     * 显示静态视锥
     */
    ShowStaticEntities()
    {
        this.RemoveAllEntities();

        //画底面
        let a_entity=Tools.BuildPolygonEntity(this.viewer,this.GetRange(),this.faceColor,this.sideColor);
        this.entities.push(a_entity);

        //画侧面和边线
        for(let i=0;i<4;i++)
        {
            let entity=Tools.BuildPolygonEntity(this.viewer,this.GetSideFace(i),this.faceColor,this.sideColor);
            this.entities.push(entity);
        }

        //画目标点
        if (this.target!=null)
        {
            this.entities.push(Tools.BuildPointEntity(this.viewer, this.target, "目标"));
        }
    }

    /**
     * 建立地面的GeometryInstance，通常用于地面摄像头的
     * @param {string} id 
     * @param {boolean} show 
     * @returns {Cesium.GeometryInstance}
     */

    BuildGroundInstance(id, show)
    {
        var ground_g = new Cesium.PolygonGeometry({
            polygonHierarchy: new Cesium.PolygonHierarchy(this.GetRange()),
            perPositionHeight: true, 
            vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
        });

        let ground_i = new Cesium.GeometryInstance({
            geometry: ground_g,
            id: id+'_frustum_ground',
            attributes: {
                color: Cesium.ColorGeometryInstanceAttribute.fromColor(this.faceColor),
                show : new Cesium.ShowGeometryInstanceAttribute(show)
            }
        });
        return ground_i;
    }

    /**
     * 建立第index的侧面GeometryInstance，通常用于地面摄像头的
     * @param {string} id 
     * @param {number} index 
     * @param {boolean} show 
     * @returns {Cesium.GeometryInstance}
     */
    BuildOneFaceInstance(id, index, show)
    {
        var side_g = new Cesium.PolygonGeometry({
            polygonHierarchy: new Cesium.PolygonHierarchy(this.GetSideFace(index)),
            perPositionHeight: true, // 如果需要多边形贴地显示，设置为true
            vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
        });
        let side_i =  new Cesium.GeometryInstance({
            geometry: side_g,
            id: id+'_frustum_face_'+index,
            attributes: {
                color: Cesium.ColorGeometryInstanceAttribute.fromColor(this.faceColor),
                show : new Cesium.ShowGeometryInstanceAttribute(show)
            }
        });
        return side_i;
    }

    /**
     * 建立第index的侧面边线GeometryInstance，通常用于地面摄像头的
     * @param {string} id 
     * @param {number} index 
     * @param {boolean} show 
     * @returns {Cesium.GeometryInstance}
     */
    BuildOneSideInstance(id, index, show)
    {
        var colors = [this.sideColor, this.sideColor, this.sideColor, this.sideColor];

        var line_g = new Cesium.SimplePolylineGeometry({
            positions: this.GetSideFaceOutline(index),
            colors: colors,
            colorsPerVertex: true
        });

        var line_i = new Cesium.GeometryInstance({
            id: id+'_frustum_side_'+index,
            geometry: line_g,
            attributes: {
                show : new Cesium.ShowGeometryInstanceAttribute(show)
            }
        });
        return line_i;
    }

    /**
     * 建立底面和侧面，通常用于地面摄像头的
     * @param {string} id 
     * @param {boolean} show 
     * @returns {Cesium.GeometryInstance[]}
     */
    BuildFaceInstances(id, show)
    {
        let instances = [];
        //添加底面
        instances.push(this.BuildGroundInstance(id, show));
        //建立侧面
        for(var i=0;i<4;i++)
        {
            instances.push(this.BuildOneFaceInstance(id,i,show));
        }
        return instances;
    }


    /**
     * 建立边线，通常用于地面摄像头的
     * @param {*} id 
     * @param {*} show 
     * @returns {Cesium.GeometryInstance[]}
     */
    BuildSideInstances(id, show)
    {
        let instances = [];
        for(var i=0;i<4;i++)
        {
            instances.push(this.BuildOneSideInstance(id,i,show));
        }
        return instances;
    }

    /**
     * 设置instance的可见性
     * @param {string} id 
     * @param {Cesium.Primitive} primitive_polygon -与面相关的instances集合primitive
     * @param {Cesium.Primitive} primitive_simpleline -与线相关的instances集合primitive
     * @param {boolean} visible 
     */
    SetInstancesVisible(id, primitive_polygon, primitive_simpleline, visible)
    {
        primitive_polygon.getGeometryInstanceAttributes(id+'_frustum_ground').show = 
            Cesium.ShowGeometryInstanceAttribute.toValue(visible);
        for(let i=0;i<4;i++)
        {
            primitive_polygon.getGeometryInstanceAttributes(id+'_frustum_face_'+i).show = 
                Cesium.ShowGeometryInstanceAttribute.toValue(visible);
        }
        for(let i=0;i<4;i++)
        {
            primitive_simpleline.getGeometryInstanceAttributes(id+'_frustum_side_'+i).show = 
                Cesium.ShowGeometryInstanceAttribute.toValue(visible);
        }
    }

    /**
     * 删除所有entities
     */
    RemoveAllEntities()
    {
        Tools.RemoveAllEntities(this.entities, this.viewer);
    }

    /**
     * 建立视锥体，带目标点     * 
     * @param {Cesium.Viewer} viewer - Cesium窗口.
     * @param {Cesium.Cartesian3} observer - 相机位置.
     * @param {Cesium.Cartesian3} target - 目标点位置.
     * @param {number} hfov - 水平视场角，角度.
     * @param {number} aspect - 宽/高.     * 
     * @param {number} uavHeading - 角度.
     * @param {number} uavStatus -  - 0:未启动；1：未飞起来；2：在空中
     * @returns {CameraFrustum} 视锥体.
     */
    static CreateFrustumByTarget(viewer, observer, target, hfov, aspect, uavHeading, uavStatus)
    {
        var heightAdjustObserver=Tools.AdjustHeight(viewer, observer);
        return CameraFrustum.CreateFrustum(viewer, heightAdjustObserver, ViewDegree.GetViewDegree(observer, target, uavHeading), 
            Cesium.Cartesian3.distance(observer,target), hfov, aspect, target, uavHeading, uavStatus);
    }

    /**
     * 建立视锥体，带观察方向
     * @param {Cesium.Viewer} viewer - Cesium窗口.
     * @param {Cesium.Cartesian3} observer - 相机位置.
     * @param {ViewDegree} viewDegree - 观察方向.
     * @param {number} hfov - 水平视场角，角度.
     * @param {number} aspect - 宽/高.
     * @param {number} uavHeading - 角度.
     * @param {number} uavStatus -  - 0:未启动；1：未飞起来；2：在空中
     * @returns {CameraFrustum} 视锥体.
     */
    static CreateFrustumByViewDegree(viewer, observer, viewDegree, hfov, aspect, uavHeading, uavSatus)
    {
        var heightAdjustObserver=Tools.AdjustHeight(viewer, observer);
        return CameraFrustum.CreateFrustum(viewer, heightAdjustObserver, viewDegree, 
            Consts.StandardFrustumSize, hfov, aspect, null, uavHeading, uavSatus);
    }

    /**
     * 建立通用视锥体
     * @param {Cesium.Viewer} viewer - Cesium窗口.
     * @param {Cesium.Cartesian3} observer - 相机位置.
     * @param {ViewDegree} viewDegree - 观察方向.
     * @param {number} frustumSize - 视锥的高度.
     * @param {number} hfov - 水平视场角，角度.
     * @param {number} aspect - 宽/高.
     * @param {Cesium.Cartesian3} target - 目标位置，可能为空.
     * @param {number} uavHeading - 角度.
     * @param {number} uavStatus -  - 0:未启动；1：未飞起来；2：在空中
     * @returns {CameraFrustum} 视锥体.
     */
    static CreateFrustum(viewer, observer, viewDegree, frustumSize, hfov, aspect, target, uavheading, uavStatus)
    {
        var surfaceCorners=[];
        if (uavStatus!=2)  //不在空中，不计算视锥
        {
            surfaceCorners.push(observer, observer, observer, observer); 
        }
        else
        {            
            //先计算标准视锥下的四个点，再旋转平移
            //标准视锥：observer为坐标原点，视锥主轴平行y轴，朝向y轴正方向
            const half_hfov=Cesium.Math.toRadians(hfov/2);
            const vfov=Tools.GetVerticalFov(hfov,aspect);
            const half_vfov=Cesium.Math.toRadians(vfov/2);   

            const delta_x=frustumSize*Math.tan(half_hfov);
            const delta_z=frustumSize*Math.tan(half_vfov);


            const referencePoint = observer;
            // 创建局部平面坐标系
            const localFrame = Cesium.Transforms.eastNorthUpToFixedFrame(referencePoint);

            //在局部坐标系
            let leftTop=new Cesium.Cartesian3(-delta_x,frustumSize,delta_z);
            let rightTop=new Cesium.Cartesian3(delta_x,frustumSize,delta_z);
            let rightDown=new Cesium.Cartesian3(delta_x,frustumSize,-delta_z);
            let leftDown=new Cesium.Cartesian3(-delta_x,frustumSize,-delta_z);

            // 定义旋转角度（以弧度为单位）
            const rotationAzimuth = Cesium.Math.toRadians(viewDegree.azimuth ? viewDegree.azimuth:0); 
            const rotationPitch = Cesium.Math.toRadians(viewDegree.pitch ? viewDegree.pitch:0); 
            const origin=new Cesium.Cartesian3(0,0,0);

            // 构造垂直旋转矩阵（绕 x 轴）
            const rotationX = Cesium.Matrix3.fromRotationX(rotationPitch);

            leftTop=Tools.RotatePointByMatrix(rotationX, origin, leftTop);
            rightTop=Tools.RotatePointByMatrix(rotationX, origin, rightTop);
            rightDown=Tools.RotatePointByMatrix(rotationX, origin, rightDown);
            leftDown=Tools.RotatePointByMatrix(rotationX, origin, leftDown);

            // 构造水平旋转矩阵（绕 z 轴）
            const rotationZ = Cesium.Matrix3.fromRotationZ(-rotationAzimuth);

            leftTop=Tools.RotatePointByMatrix(rotationZ, origin, leftTop);
            rightTop=Tools.RotatePointByMatrix(rotationZ, origin, rightTop);
            rightDown=Tools.RotatePointByMatrix(rotationZ, origin, rightDown);
            leftDown=Tools.RotatePointByMatrix(rotationZ, origin, leftDown);

            //转成全局点
            let leftTop_cartes= Cesium.Matrix4.multiplyByPoint(localFrame, leftTop, new Cesium.Cartesian3());
            let rightTop_cartes= Cesium.Matrix4.multiplyByPoint(localFrame, rightTop, new Cesium.Cartesian3());
            let rightDown_cartes= Cesium.Matrix4.multiplyByPoint(localFrame, rightDown, new Cesium.Cartesian3());
            let leftDown_cartes= Cesium.Matrix4.multiplyByPoint(localFrame, leftDown, new Cesium.Cartesian3());

            surfaceCorners.push(leftTop_cartes, rightTop_cartes, rightDown_cartes, leftDown_cartes);
        }

        return new CameraFrustum(viewer, observer, surfaceCorners,
            hfov,aspect, viewDegree, target, uavheading, uavStatus);
    }

    /**
     * 根据在视锥面上的x,y偏移比例，计算视锥面上或地表的一个位置
     * @param {number} width_factor 
     * @param {number} height_factor 
     */
    ShowPosition(width_factor, height_factor)
    {
        let pointOnSurface=this.GetPositionOnSurface(width_factor, height_factor);
        let p=Tools.PickOnEarth(this.observer, pointOnSurface);
        p=p!=null?p:pointOnSurface;
        //this.entities.push(Tools.BuildPointEntity(this.viewer, p, ""));
        return p;
    }

    /**
     * 根据在视锥面上的x,y偏移比例，计算视锥面上或地表的一个位置，并绘制一根从无人机出发的射线
     * @param {number} width_factor 
     * @param {number} height_factor 
     */
    ShowRay(width_factor, height_factor)
    {
        let pointOnSurface=this.GetPositionOnSurface(width_factor, height_factor);
        let p=Tools.PickOnEarth(this.observer, pointOnSurface);
        this.entities.push(Tools.BuildLineEntity(this.viewer, p!=null?[p, this.observer]:[pointOnSurface, this.observer]));
    }

    /**
     * 计算给定位置因子在视锥底面上的位置
     * @param {number} width_factor - 视锥平面上，以左上角为（0，0），右下角为（1，1）的水平位置，<1. 等同大疆liveview
     * @param {number} height_factor - 视锥平面上，以左上角为（0，0），右下角为（1，1）的垂直位置，<1. 等同大疆liveview
     * @returns {Cesium.Cartesian3} 在视锥上的位置.
     */
    GetPositionOnSurface(width_factor, height_factor)
    {
        const pointFromHeightMove=Cesium.Cartesian3.add(
            this.surfaceCorners[0], 
            Cesium.Cartesian3.multiplyByScalar(this.surfaceHeightUnitVector, height_factor*this.surfaceHeight, new Cesium.Cartesian3()), 
            new Cesium.Cartesian3());
        const result=Cesium.Cartesian3.add(
            pointFromHeightMove, 
            Cesium.Cartesian3.multiplyByScalar(this.surfaceWidthUnitVector, width_factor*this.surfaceWidth, new Cesium.Cartesian3()), 
            new Cesium.Cartesian3());
        return result;
    }

    /**
     * 计算视野在地表的观测范围
     * @param {Cesium.Viewer} viewer - cesium视窗.
     * @param {boolean} hasTerrain - 是否有地形.
     * @returns {GroundCast[]} 观测范围可能为null，因为看的太平了
     */    
    GetGroundCorners()
    {
        var groundCorners=[];
        const leftTop=Tools.PickOnEarth(this.observer, this.GetPositionOnSurface(0,0));
        if (leftTop==null) return null;
        groundCorners.push(leftTop); 

        const rightTop=Tools.PickOnEarth(this.observer, this.GetPositionOnSurface(1,0));
        if (rightTop==null) return null;
        groundCorners.push(rightTop); 

        const rightBottom=Tools.PickOnEarth(this.observer, this.GetPositionOnSurface(1,1));
        if (rightBottom==null) return null;
        groundCorners.push(rightBottom); 

        const leftBottom=Tools.PickOnEarth(this.observer, this.GetPositionOnSurface(0,1));
        if (leftBottom==null) return null;        
        groundCorners.push(leftBottom);         
        return groundCorners;  
    }

    /**
     * 设置第一视角视图，与视锥内容相同
     * @param {Cesium.Viewer} uavViewer 第一视角的Cesium视窗
     * @param {String} uavViewerDiv 装这个视窗的网页容器div的名字
     */
    SetUavViewer(uavViewer, uavViewerDiv)
    {
        
        if (uavViewer==null) return;
        // 获取容器元素，并设置大小
        if (uavViewerDiv!=null)
        {
            const div = document.getElementById(uavViewerDiv);
            if (div != null)
            {
                div.style.width=Consts.UavViewerSize+"px";
                const requestHeight=Math.ceil(Consts.UavViewerSize/this.aspect)+"px";
                if (requestHeight!=div.style.height)
                {
                    div.style.height = requestHeight;
                }
            }
        } 
        uavViewer.scene.camera.setView({
            destination : Tools.AdjustHeight(this.uavViewer, this.observer),
            orientation : {
                heading: this.viewDegree.azimuth?Cesium.Math.toRadians(this.viewDegree.azimuth):0,
                pitch: this.viewDegree.pitch?Cesium.Math.toRadians(this.viewDegree.pitch):0,
                roll : 0.0
            },
        });
        uavViewer.scene.camera.frustum.fov = Cesium.Math.toRadians(this.hfov);   
    }
}

/**
 * 航线绘制
 * @class
 * @description
 */
export class WayLine {
    /**
     * 构造函数
     * @param {Cesium.Viewer} _viewer - Cesium窗口.
     * @param {String} _lineMode - 航线模式，"point"：航点飞行模式；"line": 沿线航带飞行模式；"notarget": 无目标点模式
     * @param {String} _heightMode - 高度模式，"absolute"，用于有地形数据时的椭球高；"relative": 没有地形，相对于起飞点
     * @param {number} _startHeight - 在"relative"模式下的绝对起飞高度，是椭球高度
     */
    constructor(_viewer, _lineMode, _heightMode, _startHeight) {
        this.viewer = _viewer;
        this.lineMode = _lineMode;
        this.heightMode = _heightMode;
        this.startHeight = _heightMode == "relative" ? _startHeight : 0;

        this.wayLineColor = Cesium.Color.YELLOW;
        this.targetLineColor = Cesium.Color.BLUE;
        this.wayPointTargetLinkMaterial = new Cesium.PolylineGlowMaterialProperty({ color: Cesium.Color.GREEN, glowPower: 0.25, taperPower: 1 });
        this.arrowColor = Cesium.Color.RED;

        this.entities = [];
        this.wayLineEntity = null; //为了相机目标快速定位的目的
        this.wayPoints = [];
        this.targets = []; //仅用于"point"：航点飞行模式；"line": 沿线航带飞行模式
        this.viewDegrees = []; //仅用于"notarget": 无目标点模式
        this.hfovs = [];
        this.aspects = [];
        this.uav_headings = [];
    }

    /**
     * 清空所有entities
     */
    RemoveAllEntities() {
        Tools.RemoveAllEntities(this.entities, this.viewer);
        this.wayLineEntity = null;
    }

    /**
     * 
     * @param {Cesium.Cartesian3} wayPoint 
     * @param {Cesium.Cartesian3} target 
     * @returns {Cesium.Entity}
     */
    BuildWayPointTargetLinkEntity(wayPoint, target) {
        return Tools.BuildLineEntityWithMaterial(this.viewer, [wayPoint, target], this.wayPointTargetLinkMaterial);
    }

    /**
     * 
     * @param {Cesium.Cartesian3} wayPoint 
     * @param {number} wayPoint_index 
     * @returns {Cesium.Entity}
     */
    BuildWayPointEntity(wayPoint, wayPoint_index) {
        return Tools.BuildPointEntity(this.viewer, wayPoint, wayPoint_index.toString(), Cesium.Color.RED);
    }

    /**
     * 
     * @param {Cesium.Cartesian3} target 
     * @param {number} wayPoint_index 
     * @param {number} target_index 
     * @returns {Cesium.Entity}
     */
    BuildTargetEntity(target, wayPoint_index, target_index) {
        return Tools.BuildPointEntity(this.viewer, target, wayPoint_index + "-" + target_index, Cesium.Color.GREEN);
    }

    /**
     * 读入航线数据，并绘制
     * @param {key-value数组} flydata 
     * @param {String} name_camera_lng 
     * @param {String} name_camera_lat 
     * @param {String} name_camera_height 
     * @param {String} name_target_lng //仅用于"point"：航点飞行模式；"line": 沿线航带飞行模式
     * @param {String} name_target_lat //仅用于"point"：航点飞行模式；"line": 沿线航带飞行模式
     * @param {String} name_target_height //仅用于"point"：航点飞行模式；"line": 沿线航带飞行模式
     * @param {String} name_wayPoint_index wayPoint_index<0: 这是一个直线插值点，可忽略；
     * wayPoint_index=0：这是个经停拐点；wayPoint_index>0:这是个有动作的航点。
     * 在航点飞行模式下，当wayPoint_index>0时，绘制空中航点，标注wayPoint_index，绘制空中航点与地面目标点之间的连线
     * 在航带飞行模式下，当wayPoint_index>=0时，绘制空中航点与地面航点之间的连线
     * @param {String} name_target_index 在航带飞行模式下，当wayPoint_index>=0时，绘制空中航点与地面目标点之间的连线。
     * 在航点飞行模式下，当wayPoint_index>0时，绘制空中航点与地面目标点之间的连线，绘制目标点，标注wayPoint_index-targetindex,表示这个航点上需要观察的目标序号
     * @param {String} name_hfov 
     * @param {String} name_aspect 
     * @param {String} name_heading //仅用于"notarget": 无目标点模式
     * @param {String} name_pitch //仅用于"notarget": 无目标点模式
     * @returns 
     */
    BuildEntities(flydata,
        name_camera_lng = "camera_lng", name_camera_lat = "camera_lat", name_camera_height = "camera_ground_height",
        name_target_lng = "target_lng", name_target_lat = "target_lat", name_target_height = "target_ground_height",
        name_wayPoint_index = "sky_index", name_target_index = "target_index", name_hfov = "fieldOfView", name_aspect = "aspect",
        name_heading = "platform_heading", name_pitch = "platform_pitch"
    ) {
        this.wayPoints.length = 0;
        this.targets.length = 0;
        this.viewDegrees.length = 0;
        this.hfovs.length = 0;
        this.aspects.length = 0;
        this.RemoveAllEntities();

        let arrow_positions = [];  //专用于画箭头的，因为需要稍微高一点，否则就被航线遮住了


        for (let i = 0; i < flydata.length; i++) {
            var _data = flydata[i];

            var sky_index = _data[name_wayPoint_index];
            var sky_position = Tools.GetCartesian3FromDegreesHeight(_data[name_camera_lng], _data[name_camera_lat], _data[name_camera_height] + this.startHeight);
            this.wayPoints.push(sky_position);

            this.hfovs.push(_data[name_hfov]);
            this.aspects.push(_data[name_aspect]);

            if (this.lineMode != "notarget") {
                var target_index = _data[name_target_index];
                var target_position = Tools.GetCartesian3FromDegreesHeight(_data[name_target_lng], _data[name_target_lat], _data[name_target_height] + this.startHeight);
                this.targets.push(target_position);  //由于地面情况比较复杂，就全部绘制吧
            }
            else {
                this.viewDegrees.push(new ViewDegree(_data[name_heading], _data[name_pitch]));
            }

            if (sky_index >= 0)  //不是直线插值点
            {
                let arrow_position = Tools.GetCartesian3FromDegreesHeight(_data[name_camera_lng], _data[name_camera_lat], _data[name_camera_height] + this.startHeight + 0.4);
                arrow_positions.push(arrow_position);

                if (sky_index > 0 && this.lineMode == "point")  //针对航点模式，需要添加空中点和地面点,skyindex=0就不绘制了，因为那是非动作点
                {
                    var wp = Tools.AdjustHeight(this.viewer, sky_position);
                    let skyEntity = this.BuildWayPointEntity(wp, sky_index);
                    this.entities.push(skyEntity);

                    var tp = Tools.AdjustHeight(this.viewer, target_position);
                    let targetEntity = this.BuildTargetEntity(tp, sky_index, target_index);  //为了标识是哪个航点的target，所以，sky_index也传递了
                    this.entities.push(targetEntity);

                    let linkEntity = this.BuildWayPointTargetLinkEntity(wp, tp);
                    this.entities.push(linkEntity);

                }
                if (this.lineMode == "line")  //针对航带模式，就绘制所有弯折处的空地连线
                {
                    let linkEntity = this.BuildWayPointTargetLinkEntity(sky_position, target_position);
                    this.entities.push(linkEntity);
                }
            }

        }

        //建立空中航线实体
        var adjustVs = Tools.AdjustManyHeight(this.viewer, this.wayPoints);
        this.wayLineEntity = Tools.BuildLineEntity(this.viewer, adjustVs, this.wayLineColor, 2);
        this.entities.push(this.wayLineEntity);

        //如果是沿线飞行，则把地面的线也画一下
        if (this.lineMode == "line") {
            adjustVs = Tools.AdjustManyHeight(this.viewer, this.targets);
            let targetLineEntity = Tools.BuildLineEntity(this.viewer, adjustVs, this.targetLineColor, 2);
            this.entities.push(targetLineEntity);
        }
        // 尝试添加箭头
        const ArrowHalfHeight = 6;
        const ArrowHalfWidth = 3;
        const MinLengthForArrow = 30;

        for (let t = 0; t < arrow_positions.length - 1; t++) {
            var distance = Cesium.Cartesian3.distance(arrow_positions[t], arrow_positions[t + 1]);
            if (distance < MinLengthForArrow) continue;  //线段太短，没法画箭头
            var midPoint = Cesium.Cartesian3.lerp(arrow_positions[t], arrow_positions[t + 1], 0.5, new Cesium.Cartesian3());
            var direction = Cesium.Cartesian3.subtract(arrow_positions[t + 1], arrow_positions[t], new Cesium.Cartesian3());
            Cesium.Cartesian3.normalize(direction, direction);

            // 地面的法向量
            var groundNormal = midPoint;
            // 计算垂直于给定向量且平行于地面的向量
            var horizontalDirection = Cesium.Cartesian3.cross(direction, groundNormal, new Cesium.Cartesian3());
            Cesium.Cartesian3.normalize(horizontalDirection, horizontalDirection);

            var headPoint = Tools.GetPositionAlongVector(midPoint, direction, ArrowHalfHeight);
            var tailCenterPoint = Tools.GetPositionAlongVector(midPoint, direction, -ArrowHalfHeight);
            var cornerPoint1 = Tools.GetPositionAlongVector(tailCenterPoint, horizontalDirection, ArrowHalfWidth);
            var cornerPoint2 = Tools.GetPositionAlongVector(tailCenterPoint, horizontalDirection, -ArrowHalfWidth);
            var tailPoint = Tools.GetPositionAlongVector(tailCenterPoint, direction, ArrowHalfWidth);

            var arrowPoints = [headPoint, cornerPoint1, tailPoint, cornerPoint2];
            adjustVs = Tools.AdjustManyHeight(this.viewer, arrowPoints);
            var a_entity = Tools.BuildPolygonEntity(this.viewer, adjustVs, this.arrowColor);
            this.entities.push(a_entity);
        }
        this.LocateWayLine();
        return this.entities;
    }

    /**
     * 设置沿线的无人机方向和姿态
     * @param {number} modelOffsetDegree 模型如果不冲北，就要加偏转角
     */
    SetHeading() {
        this.uav_headings.length = 0;
        for (let i = 1; i < this.wayPoints.length; i++) {
            var heading = Tools.HeadingPointToPoint(this.wayPoints[i - 1], this.wayPoints[i]);
            this.uav_headings.push(heading);
        }
        this.uav_headings.push(this.uav_headings[this.uav_headings.length - 1]); //复制最后一个heading
    }

    /**
     * 定位到航线
     */
    LocateWayLine() {
        this.viewer.zoomTo(this.wayLineEntity);
    }

    /**
     * 插值扩充数据集，是为了让航线更加平滑
     * @param {number} times 扩充的倍数，通常是从1秒变成100ms，就是扩充10倍
     */
    Expand(times = 10) {
        var _big_sky_positions = [];
        var _big_viewdegrees = [];
        var _big_target_positions = [];
        var _big_hfovs = [];
        var _big_aspects = []
        for (let i = 0; i < this.wayPoints.length - 1; i++) {
            for (let j = 0; j < times; j++) {
                _big_sky_positions.push(Tools.InterpolateCartesian3(
                    this.wayPoints[i], this.wayPoints[i + 1], j / times));
                if (this.lineMode != "notarget") {
                    _big_target_positions.push(Tools.InterpolateCartesian3(
                        this.targets[i], this.targets[i + 1], j / times));
                }
                else {
                    _big_viewdegrees.push(ViewDegree.Interpolate(this.viewDegrees[i], this.viewDegrees[i + 1], j / times));
                }
                _big_hfovs.push(Tools.InterpolateNumber(
                    this.hfovs[i], this.hfovs[i + 1], j / times));
                _big_aspects.push(Tools.InterpolateNumber(
                    this.aspects[i], this.aspects[i + 1], j / times));
            }
        }

        _big_sky_positions.push(this.wayPoints[this.wayPoints.length - 1]);

        if (this.lineMode != "notarget") {
            _big_target_positions.push(this.targets[this.wayPoints.length - 1]);
        }
        else {
            _big_viewdegrees.push(this.viewDegrees[this.wayPoints.length - 1]);
        }

        _big_hfovs.push(this.hfovs[this.wayPoints.length - 1]);
        _big_aspects.push(this.aspects[this.wayPoints.length - 1]);

        this.wayPoints = _big_sky_positions;
        this.targets = _big_target_positions;
        this.viewDegrees = _big_viewdegrees;
        this.hfovs = _big_hfovs;
        this.aspects = _big_aspects;
    }

    /**
     * 
     * @returns {number} 航点数，如果调用了Expand，那就是扩充后的点数
     */
    GetCount() {
        return this.wayPoints.length;
    }
}

/**
 * 模拟飞行
 */
export class SimuFly {
    /**
     * 
     * @param {Cesium.Viewer} _godViewer 
     * @param {Cesium.Viewer} _uavViewer 
     * @param {String} _uavViewerDiv 容纳_uavViewer的div 
     * @param {String} _uavModeluri 
     * @param {number} _modelOffsetDegree 
     * @param {String} _lineMode - 航线模式，"point"：航点飞行模式；"line": 沿线航带飞行模式; "notarget": 无目标点模式
     * @param {String} _heightMode - 高度模式，"absolute"，用于有地形数据时的椭球高；"relative": 没有地形，相对于地面高度，此时目标点高度都为0
     * @param {number} _startHeight - 在"relative"模式下的绝对起飞高度，是椭球高度
     */
    constructor(
        _godViewer, _uavViewer, _uavViewerDiv,
        _uavModeluri, _modelOffsetDegree,
        _lineMode, _heightMode, _startHeight) {
        this.godViewer = _godViewer;
        this.uavViewer = _uavViewer;
        this.uavViewerDiv = _uavViewerDiv;
        this.uavModeluri = _uavModeluri;
        this.modelOffsetDegree = _modelOffsetDegree;

        this.wayLine = new WayLine(_godViewer, _lineMode, _heightMode, _startHeight);
        this.showFrustum = null;

        this.AllSteps = [];       //所有视锥

        this.running = false;
        this.IntervalID = -1;

        this.TimeChangeEventName = "TimeChanged";
        this.TimeChangedEvent = new CustomEvent(this.TimeChangeEventName, {});

        this.PrepareDoneEventName = "PrepareDone";
        this.PrepareDoneEvent = new CustomEvent(this.PrepareDoneEventName, {});

        this.currentStep = 0;
        this.StepCountEachTick = 1;
    }

    /**
     * 删除所有实体
     */
    RemoveAllEntities() {
        if (this.showFrustum != null) this.showFrustum.RemoveAllEntities();
        this.wayLine.RemoveAllEntities();
    }

    /**
     * 
     * @param {key-value数组} flydata 
     * @param {String} name_camera_lng 
     * @param {String} name_camera_lat 
     * @param {String} name_camera_ground_height 
     * @param {String} name_target_lng //仅用于"point"：航点飞行模式；"line": 沿线航带飞行模式
     * @param {String} name_target_lat //仅用于"point"：航点飞行模式；"line": 沿线航带飞行模式
     * @param {String} name_target_ground_height //仅用于"point"：航点飞行模式；"line": 沿线航带飞行模式
     * @param {String} name_fov 
     * @param {String} name_aspect 
     * @param {String} name_skyIndex 
     * @param {String} name_targetIndex 
     * @param {String} name_heading //仅用于"notarget": 无目标点模式
     * @param {String} name_pitch //仅用于"notarget": 无目标点模式
     */
    Prepare(flydata,
        name_camera_lng = "camera_lng", name_camera_lat = "camera_lat", name_camera_ground_height = "camera_ground_height",
        name_target_lng = "target_lng", name_target_lat = "target_lat", name_target_ground_height = "target_ground_height",
        name_hfov = "camera_fieldofview", name_aspect = "aspect",
        name_skyIndex = "sky_index", name_targetIndex = "target_index",
        name_heading = "platform_heading", name_pitch = "platform_pitch"
    ) {
        this.RemoveAllEntities();
        this.AllSteps.length = 0;

        this.wayLine.BuildEntities(flydata,
            name_camera_lng, name_camera_lat, name_camera_ground_height,
            name_target_lng, name_target_lat, name_target_ground_height,
            name_skyIndex, name_targetIndex, name_hfov, name_aspect,
            name_heading, name_pitch);
        //插值
        this.wayLine.Expand(10);
        //设置无人机方向和姿态
        this.wayLine.SetHeading();

        for (let i = 0; i < this.wayLine.GetCount(); i++) {
            if (this.wayLine.lineMode != "notarget") {
                this.AllSteps.push(CameraFrustum.CreateFrustumByTarget(this.godViewer,
                    this.wayLine.wayPoints[i], this.wayLine.targets[i],
                    this.wayLine.hfovs[i], this.wayLine.aspects[i],
                    this.wayLine.uav_headings[i], 2));
            }
            else {
                this.AllSteps.push(CameraFrustum.CreateFrustumByViewDegree(this.godViewer,
                    this.wayLine.wayPoints[i], this.wayLine.viewDegrees[i],
                    this.wayLine.hfovs[i], this.wayLine.aspects[i],
                    this.wayLine.uav_headings[i], 2));
            }
        }

        this.currentStep = 0;
        this.showFrustum = new ShowFrustum(this.godViewer, this.uavModeluri, this.modelOffsetDegree, "", this.AllSteps[this.currentStep]);
        this.SetUavViewer();
        window.dispatchEvent(this.PrepareDoneEvent);
    }

    /**
     * 同步到第一视角
     */
    SetUavViewer() {
        var frustum = this.AllSteps[this.currentStep];
        frustum.SetUavViewer(this.uavViewer, this.uavViewerDiv);
    }

    /**
     * 重新回到第一步
     */
    Reset() {
        this.currentStep = 0;
        this.showFrustum.UpdateFrustum(this.AllSteps[this.currentStep]);
        window.dispatchEvent(this.TimeChangedEvent);
    }

    /**
     * 开启自动播放
     * @param {number} _stepInterval 间隔时间
     * @param {number} _stepCount 跳帧数
     * @param {boolean} _loop 是否循环
     * @returns 
     */
    StartAnimation(_stepInterval, _stepCount, _loop) {
        if (this.running) return;
        if (this.currentStep >= this.AllSteps.length - 1) {
            this.currentStep = 0;
            window.dispatchEvent(this.TimeChangedEvent);
        }
        this.running = true;
        this.StepCountEachTick = _stepCount;
        //
        this.godViewer.trackedEntity = this.showFrustum.uavEntity;
        //Tools.SetViewType(this.godViewer, 1, this.showFrustum.uavEntity, this.AllSteps[this.currentStep].observer);
        this.IntervalID = setInterval(() => {
            this.currentStep = Math.min(this.currentStep + this.StepCountEachTick, this.AllSteps.length - 1);
            if (this.currentStep >= this.AllSteps.length - 1) {
                if (_loop == false) {
                    this.currentStep = this.AllSteps.length - 1;
                    this.StopAnimation();
                }
                else {
                    this.currentStep = 0;
                }
            }
            this.showFrustum.UpdateFrustum(this.AllSteps[this.currentStep]);
            //this.godViewer.trackedEntity = this.showFrustum.uavEntity;
            window.dispatchEvent(this.TimeChangedEvent);
            this.SetUavViewer();

        }, _stepInterval);
    }

    /**
     * 停止自动播放      
     */
    StopAnimation() {
        if (!this.running) return;
        this.running = false;
        clearInterval(this.IntervalID);
        this.godViewer.trackedEntity = undefined;
    }

    /**
     * 设置跳帧数
     * @param {number} stepCountEachTick 
     */
    SetStepCountEachTick(stepCountEachTick) {
        this.StepCountEachTick = stepCountEachTick;
    }

    /**
     * 设置当前步数
     * @param {number} step 
     * @returns 
     */
    SetCurrentStep(step) {
        step = parseInt(step);
        if (parseInt(this.currentStep) == step) return;
        if (this.AllSteps.length == 0) return;
        this.currentStep = Math.min(this.AllSteps.length - 1, step);
        this.showFrustum.UpdateFrustum(this.AllSteps[this.currentStep]);
        if (this.running == false) {
            this.SetUavViewer();
        }
    }

    /**
     * 获得总步数
     * @returns {number}
     */
    GetTotal() {
        return Math.max(0, this.AllSteps.length - 1);
    }

    /**
     * 获得当前步数
     * @returns {number}
     */
    GetCurrent() {
        return parseInt(this.currentStep);
    }
}



/**
 * 一条视频记录
 */
class OneVideoRecord
{    
    /**
     * 
     * @param {number} _start_time 开始时间，以毫秒为单位，unix时间
     * @param {number} _duration_seconds 视频时长，以秒为单位
     * @param {string} _url 视频的URL地址
     */
    constructor(
            _start_time,        //开始时间，以毫秒为单位
            _duration_seconds,  //视频时长，以秒为单位
            _url                //视频的URL地址
        )
    {
        this.start_time=_start_time;
        this.duration_seconds=_duration_seconds;
        this.url=_url;
        this.stop_time=this.start_time+this.duration_seconds*1000;
    }
}

/**
 * 历史任务回放
 */
export class PlayHistory{

    /**
     * 
     * @param {Cesium.Viewer} _mapViewer 
     * @param {String} _container -视频播放器所在的窗口div的id值，字符串，如<div class="videoclass" id="VideoWindow"></div>中的"VideoWindow"
     * @param {String} _uavModeluri -uav模型地址
     * @param {number} _modelOffsetDegree -我们希望无人机方位角为0时，朝正北，但无人机模型可能是歪的，所以需要一个旋转偏移值，角度
     */
    constructor(_mapViewer, _container, _uavModeluri, _modelOffsetDegree)
    {
        this.mapViewer=_mapViewer;        
        this.player = document.getElementById(_container);
        this.uavModeluri=_uavModeluri;
        this.modelOffsetDegree=_modelOffsetDegree;


        this.start_time=0;  //毫秒
        this.stop_time=1000;//毫秒
        
        this.Current100ms=0;  //以100ms为单位，记录当前的步数
        this.IntervalID=-1;
        this.PlayBeginTime=null;
        this.PlayBeginStep=0;

        this.TimeChangeEventName="TimeChanged";
        this.TimeChangedEvent = new CustomEvent(this.TimeChangeEventName, {});

        this.PrepareDoneEventName="PrepareDone";
        this.PrepareDoneEvent = new CustomEvent(this.PrepareDoneEventName, {});

        this.InterpolatedFlyRecords=[];
    }

    /**
     * 
     * @param {key-value数组} videodata -记录视频文件的数组，以下为数组中的key名字 
     * @param {key-value数组} flydata -记录飞行的数组，以下为数组中的key名字    
     * @param {string} _name_start_time -视频数组中起始时间戳，自 1970 年 1 月 1 日 00:00:00 UTC（协调世界时）以来经过的毫秒数（时间戳）。
     * @param {string} _name_time_length -时长，单位是秒
     * @param {string} _name_request_url -视频URL
     * @param {string} _name_time -飞行数组中时间戳：自 1970 年 1 月 1 日 00:00:00 UTC（协调世界时）以来经过的毫秒数（时间戳）。 
     * @param {string} _name_uav_status - 当为0:未启动；1：未飞起来；2：在空中
     * @param {string} _name_uav_sn 
     * @param {string} _name_uav_lng 
     * @param {string} _name_uav_lat 
     * @param {string} _name_uav_height 
     * @param {string} _name_uav_heading 
     * @param {string} _name_platform_azimuth 
     * @param {string} _name_platform_pitch 
     * @param {string} _name_camera_hfov 
     * @param {string} _name_camera_aspect 
     */
    PrepareData(
        videodata,          //记录视频文件的数组，以下为数组中的key名字 
        flydata,            //记录飞行的数组，以下为数组中的key名字     

        _name_start_time="start_time",   
        _name_time_length="time_length", 
        _name_request_url="request_url", 
  
        _name_time="time", 
        _name_uav_status="uav_status", _name_uav_lng="uav_lng", _name_uav_lat="uav_lat", 
        _name_uav_height="uav_height", _name_uav_heading="uav_heading",
        _name_platform_azimuth="platform_azimuth", _name_platform_pitch="platform_pitch",
        _name_camera_hfov="camera_hfov", _name_camera_aspect="camera_aspect"
    )
    {
        this.Quit();
        this.PrepareVideoData(videodata, _name_start_time, _name_time_length, _name_request_url);
        this.PrepareFlyData(flydata, _name_time, _name_uav_status, _name_uav_lng, _name_uav_lat, 
            _name_uav_height, _name_uav_heading, _name_platform_azimuth, _name_platform_pitch,
            _name_camera_hfov, _name_camera_aspect);
        this.Syncronize();
    }

    /**
     * 第一步：读视频数据
     * @param {key-value数组} videodata 
     * @param {string} _name_start_time 
     * @param {string} _name_time_length 
     * @param {string} _name_request_url 
     */
    PrepareVideoData(videodata, _name_start_time, _name_time_length, _name_request_url)
    {
        this.VideoRecords=[];
        for(let i=0;i<videodata.length;i++)
        {
            let _data=videodata[i];
            var _start_time=_data[_name_start_time];
            var _time_length=_data[_name_time_length];
            var _request_url=_data[_name_request_url];
            this.VideoRecords.push(new OneVideoRecord(_start_time,_time_length,_request_url));
        }
        this.video_start_time=this.VideoRecords[0].start_time;
        this.video_stop_time=this.VideoRecords[this.VideoRecords.length-1].stop_time;
    }

    /**
     * 第二步：读飞行数据
     * @param {key-value数组} flydata 
     * @param {string} _name_time 
     * @param {string} _name_uav_status 
     * @param {string} _name_uav_lng 
     * @param {string} _name_uav_lat 
     * @param {string} _name_uav_height 
     * @param {string} _name_uav_heading 
     * @param {string} _name_platform_azimuth 
     * @param {string} _name_platform_pitch 
     * @param {string} _name_camera_hfov 
     * @param {string} _name_camera_aspect 
     */
    PrepareFlyData(flydata, _name_time, _name_uav_status, _name_uav_lng, _name_uav_lat, 
            _name_uav_height, _name_uav_heading, _name_platform_azimuth, _name_platform_pitch,
            _name_camera_hfov, _name_camera_aspect)
    {   
        this.FlyRecords=[];

        for(let i=0;i<flydata.length;i++)
        {
            let statusData=flydata[i];
            var uav_status = statusData[_name_uav_status];

            var uav_lng=statusData[_name_uav_lng];
            var uav_lat=statusData[_name_uav_lat];
            var uav_height=statusData[_name_uav_height];
            var uav_heading = statusData[_name_uav_heading];

            var platform_azimuth = statusData[_name_platform_azimuth];
            var platform_pitch = statusData[_name_platform_pitch];
            
            var camera_hfov = statusData[_name_camera_hfov];
            var camera_aspect = statusData[_name_camera_aspect];

            var observer=Tools.GetCartesian3FromDegreesHeight(uav_lng, uav_lat, uav_height);

            var frustum=CameraFrustum.CreateFrustumByViewDegree(this.mapViewer, 
                observer, 
                new ViewDegree(platform_azimuth, platform_pitch), 
                camera_hfov, camera_aspect, uav_heading, uav_status);
            frustum.SetTime(statusData[_name_time]);
            this.FlyRecords.push(frustum);
        }  

        this.fly_start_time=this.FlyRecords[0].time;
        this.fly_stop_time=this.FlyRecords[this.FlyRecords.length-1].time;

    }

    /**
     * 第三步：完成数据对齐和插值，对齐视频和飞行数据，找二者重叠的时间段
     */
    Syncronize()
    {
        this.start_time=Math.max(this.fly_start_time,this.video_start_time);
        this.stop_time=Math.min(this.fly_stop_time,this.video_stop_time)

        //绘制航线
        var originLocations=[];
        for(let i=0;i<this.FlyRecords.length;i++)
        {
            if (this.FlyRecords[i].time<this.start_time) continue;
            if (this.FlyRecords[i].time>this.stop_time) break;   
            originLocations.push(this.FlyRecords[i].observer);
        }
        this.RemoveAllEntities();
        this.lineEntity=Tools.BuildLineEntity(this.mapViewer, originLocations);
        this.mapViewer.zoomTo(this.lineEntity);

        //按照100ms的间隔插值飞行数据
        this.InterpolatedFlyRecords=[];
        for(let time=this.start_time;time<this.stop_time;time+=100) 
        {
            var newStatus=null;
            if (time<=this.FlyRecords[0].time)
            {
                newStatus=this.FlyRecords[0];
            }
            else if (time>=this.FlyRecords[this.FlyRecords.length-1].time)
            {
                newStatus=this.FlyRecords[this.FlyRecords.length-1];
            }
            else
            {
                var index=0;
                for(let p=0;p<this.FlyRecords.length-1;p++)
                {
                    if (time>this.FlyRecords[p].time&&time<=this.FlyRecords[p+1].time)
                    {
                        index=p;
                        break;
                    }
                }                
                newStatus=CameraFrustum.InterpolateByTime(this.FlyRecords[index],this.FlyRecords[index+1],time);
            }            
            this.InterpolatedFlyRecords.push(newStatus);
        }
        this.Current100ms=0;
        
        this.showFrustum=new ShowFrustum(this.mapViewer, this.uavModeluri, this.modelOffsetDegree,"",
            this.InterpolatedFlyRecords[this.Current100ms]);

        this.player.currentTime=0;
        window.dispatchEvent(this.PrepareDoneEvent);
        window.dispatchEvent(this.TimeChangedEvent);
        this.Begin(); //开启视频和位置同步机制
    }

    /**
     * 移除所有实体
     */
    RemoveAllEntities()
    {
        this.mapViewer.entities.remove(this.lineEntity);  //航线
        if (this.showFrustum!=null)
            this.showFrustum.RemoveAllEntities();
    }

    /**
     * 获得最大的步骤序号
     * @returns {number}
     */
    GetMaxStepIndex()
    {
        return this.InterpolatedFlyRecords.length-1;
    }

    /**
     * 来自前端的调整，修改当前时间为前端进度条设置的时间，单位是秒
     * @param {number} stepIndex 
     */
    SetCurrentStep(stepIndex)
    {
        if (stepIndex!=this.Current100ms) 
        {
            this.Pause();
            if (this.InterpolatedFlyRecords.length>0)
            {
                this.Current100ms=stepIndex;
            }
        }
    }

    /**
     * 获得插值数组中当前时间的记录，该插值数组的记录是每100ms记录一次
     * @returns {number}
     */
    GetCurrentStep()
    {
        return this.Current100ms;
    }

    /**
     * 当前端滚动条发生变化时
     * @param {number} stepIndex 前端滚动条的value，以100ms为单位
     */
    AfterValueChanged(stepIndex)
    {
        this.Current100ms=stepIndex;
        this.Seek();
        this.showFrustum.UpdateFrustum(this.InterpolatedFlyRecords[this.Current100ms]);
    }


    /**
     * 获得当前播放的绝对时间
     * @returns {Date}
     */
    GetCurrentTime()
    {
        //参数是毫秒，所以Current100ms这种一秒10次的变量就要*100才能变成秒
        var date=new Date(this.start_time+this.Current100ms*100);  
        return date;
    }

    /**
     * 输出格式化的时间
     * @returns {string}
     */
    GetCurrentTimeFormat()
    {
        var date=this.GetCurrentTime();
        var year=date.getFullYear();        
        var month=date.getMonth()+1;
        if (month<10) month="0"+month;
        var day=date.getDate();
        if (day<10) day="0"+day;
        var hour=date.getHours();
        if (hour<10) hour="0"+hour;
        var minute=date.getMinutes();
        if (minute<10) minute="0"+minute;
        var second=date.getSeconds();
        if (second<10) second="0"+second;

        return year+"-"+month+"-"+day+" "+hour+":"+minute+":"+second;
    }

    /**
     * 判断是否有插值记录
     * @returns {boolean}
     */
    IsValid()
    {
        return this.InterpolatedFlyRecords.length>0;
    }
    


    /**
     * 获得当前回放时刻的无人机位置
     * @returns {Cesium.Cartesian3}
     */
    GetLatestPosition(){
        var onestatus = this.InterpolatedFlyRecords[this.Current100ms];
        return onestatus.observer;
    }

    /**
     * 根据当前时刻，找到对应视频段的视频帧，将播放时间设置到这个帧，一个长的飞行过程可能涉及多个视频段
     */
    Seek(){
        var video_time = this.start_time + this.Current100ms * 100;
        for(let i=0;i<this.VideoRecords.length;i++)
        {
            if (video_time>=this.VideoRecords[i].start_time && video_time<=this.VideoRecords[i].stop_time)
            {
                this.currentVideoIndex=i;
                var pos=parseInt(video_time-this.VideoRecords[this.currentVideoIndex].start_time);
                this.player.src=this.VideoRecords[this.currentVideoIndex].url;
                this.player.currentTime=pos/1000;
                break;
            }
        }

    }

    /**
     * 启动一个定时器，根据播放情况获得当前时刻，切换视频段，更新Current100ms
     */
    Begin(){
        this.IntervalID=setInterval(()=>{
            if (this.player.paused) return;
            if (this.player.currentTime>=this.player.duration)//本集播放完了
            {
                if (this.currentVideoIndex<this.VideoRecords.length-1)//播放下一集
                {
                    this.currentVideoIndex++;    
                    this.player.src=this.VideoRecords[this.currentVideoIndex].url; 
                    this.player.play();
                }
            }
            else //更新时间
            {
                var step=parseInt((this.player.currentTime*1000+this.VideoRecords[this.currentVideoIndex].start_time-this.start_time)/100);
                
                this.Current100ms = Math.min(step, this.InterpolatedFlyRecords.length - 1);
                this.showFrustum.UpdateFrustum(this.InterpolatedFlyRecords[this.Current100ms]);
                //console.log("step:"+step);
                if (step>=this.InterpolatedFlyRecords.length-1)
                {
                    this.Pause();
                }
                window.dispatchEvent(this.TimeChangedEvent);
            }            
            
        },100);
    }

    /**
     * 前端要求播放
     */
    Play()
    {
        if (this.Current100ms>=this.InterpolatedFlyRecords.length-1)
        {
            this.Current100ms=0;
            window.dispatchEvent(this.TimeChangedEvent);
        }
        this.mapViewer.trackedEntity = this.showFrustum.uavEntity;
        this.Seek();
        this.player.play();
    }

    /**
     * 前端要求暂停
     */
    Pause()
    {
        this.mapViewer.trackedEntity = undefined;
        this.player.pause();
    }

    /**
     * 前端要求倍速播放
     * @param {number} v 
     */
    SetSpeed(v) {
        this.player.playbackRate=v;
    }

    /**
     * 清除此次历史飞行记录播放占用的资源
     */
    Quit()
    {
        if (this.IntervalID>=0) clearInterval(this.IntervalID);
        this.IntervalID=-1;
        this.RemoveAllEntities();
        this.showFrustum=null;
        this.Pause();
    }
}

/**
 * 显示实时收到的无人机状态
 */
export class ShowStatus{
    /**
     * 
     * @param {Cesium.Viewer} _godViewer 
     * @param {String} _uavModeluri 
     * @param {number} _modelOffsetDegree 
     * @param {String} _name_uav_status - 当为0:未启动；1：未飞起来；2：在空中
     * @param {String} _name_uav_sn 
     * @param {String} _name_uav_lng 
     * @param {String} _name_uav_lat 
     * @param {String} _name_uav_height 
     * @param {String} _name_uav_heading 
     * @param {String} _name_platform_azimuth 
     * @param {String} _name_platform_pitch 
     * @param {String} _name_camera_hfov 
     * @param {String} _name_camera_aspect 
     */
    constructor(_godViewer, _uavModeluri, _modelOffsetDegree,      
        _name_uav_status="uav_status", 
        _name_uav_sn="uav_sn", _name_uav_lng="uav_lng", _name_uav_lat="uav_lat", 
        _name_uav_height="uav_height", _name_uav_heading="uav_heading",
        _name_platform_azimuth="platform_azimuth", _name_platform_pitch="platform_pitch",
        _name_camera_hfov="camera_hfov", _name_camera_aspect="camera_aspect"
    )
    {
        this.godViewer=_godViewer;
        this.uavModeluri=_uavModeluri;
        this.modelOffsetDegree=_modelOffsetDegree;
        
        this.name_uav_status=_name_uav_status;

        this.name_uav_sn=_name_uav_sn;

        this.name_uav_lng=_name_uav_lng;
        this.name_uav_lat=_name_uav_lat;
        this.name_uav_height=_name_uav_height;        
        this.name_uav_heading = _name_uav_heading;

        this.name_platform_azimuth=_name_platform_azimuth;
        this.name_platform_pitch = _name_platform_pitch;

        this.name_camera_hfov=_name_camera_hfov;
        this.name_camera_aspect=_name_camera_aspect;

        this.all_uavs=new Map();  //key: UAV's sn, Value: showfrustum


    }

    /**
     * 获得某个无人机的视锥
     * @param {string} uavSn 
     * @returns 
     */
    GetFrustum(uavSn)
    {
        if (this.all_uavs.has(uavSn))
        {
            return this.all_uavs.get(uavSn).frustum;
        }
        return null;
    }

    /**
     * 实时接收到服务器发来的飞机状态数据
     * @param {key-value} statusData 
     */
    AddData(statusData){

        var uav_status = statusData[this.name_uav_status];
        var sn=statusData[this.name_uav_sn];

        var uav_lng=statusData[this.name_uav_lng];
        var uav_lat=statusData[this.name_uav_lat];
        var uav_height=statusData[this.name_uav_height];
        var uav_heading = statusData[this.name_uav_heading];

        var platform_azimuth = statusData[this.name_platform_azimuth];
        var platform_pitch = statusData[this.name_platform_pitch];
        
        var camera_hfov = statusData[this.name_camera_hfov];
        var camera_aspect = statusData[this.name_camera_aspect];

        var observer=Tools.GetCartesian3FromDegreesHeight(uav_lng, uav_lat, uav_height);

        var frustum=CameraFrustum.CreateFrustumByViewDegree(this.godViewer, 
            observer, 
            new ViewDegree(platform_azimuth, platform_pitch), 
            camera_hfov, camera_aspect, uav_heading, uav_status);


        if (this.all_uavs.has(sn))
        {
            this.all_uavs.get(sn).UpdateFrustum(frustum);
        }
        else
        {
            this.all_uavs.set(sn, new ShowFrustum(this.godViewer, this.uavModeluri, this.modelOffsetDegree,sn, frustum));
        }
    }

    /**
     * 启动一个定时器，用于检查飞机是否离线
     */
    Start()
    {
        this.Stop();
        this.IntervalID=setInterval(()=>{
            for (let value of this.all_uavs) {
                value.CheckOffline();
            }
        },Consts.OfflineThreshold);
    }

    /**
     * 清理占用的资源
     */
    Stop()
    {
        clearInterval(this.IntervalID);
        for (let value of this.all_uavs) {
            value.RemoveAllEntities();
        }
        this.all_uavs.clear();
    }
}

/**
 * 地面相机
 */
class OneGroundCamera
{
    /**
     * 
     * @param {Cesium.Viewer} viewer - 视窗
     * @param {string} id - 相机id
     * @param {string} label - 相机label
     * @param {Cartesian3} basePosition  -位置 
     * @param {number} polarHeight -杆子高度
     * @param {ViewDegree} viewDegree -视角
     * @param {number} hfov -
     * @param {number} aspect 
     */
    constructor(viewer, id, label, basePosition, polarHeight, viewDegree, 
        hfov, aspect)
    {
        this.viewer=viewer;
        this.id=id;
        this.label=label;
        this.basePosition=basePosition;
        this.poleHeight=polarHeight;
        this.labelOffset=1;
        this.topPosition=Tools.MoveOnEarth(this.basePosition,0,0,this.poleHeight);
        this.labelPosition=Tools.MoveOnEarth(this.topPosition,0,0,this.labelOffset);
        this.viewDegree=viewDegree;
        this.hfov=hfov;
        this.aspect=aspect;
        this.frustum=null;
        this.showFrustum=new ShowFrustum(this.viewer, "", 0, "");
    } 

    /**
     * 更新视锥
     */
    UpdateFrustum()
    {
        this.frustum=CameraFrustum.CreateFrustumByViewDegree(this.viewer,this.topPosition,this.viewDegree,this.hfov,this.aspect,
            this.viewDegree.azimuth,2);
        this.showFrustum.UpdateFrustum(this.frustum);
    }

    /**
     * 调节观测方向
     * @param {number} deltaAzimuth 
     * @param {number} deltaPitch 
     */
    AdjustViewDegree(deltaAzimuth, deltaPitch)
    {
        this.viewDegree = new ViewDegree(this.viewDegree.azimuth ? this.viewDegree.azimuth : 0 + deltaAzimuth, this.viewDegree.pitch ? this.viewDegree.pitch :0+deltaPitch);
        this.UpdateFrustum();
    }

    /**
     * 地面移动相机杆子
     * @param {number} deltaXmeters 
     * @param {number} deltaYmeters 
     * @param {number} deltaZmeters 
     */
    AdjustBasePosition(deltaXmeters, deltaYmeters, deltaZmeters)
    {
        this.basePosition=Tools.MoveOnEarth(this.basePosition,deltaXmeters,deltaYmeters,deltaZmeters);
        this.topPosition=Tools.MoveOnEarth(this.basePosition,0,0,this.poleHeight);
        this.labelPosition=Tools.MoveOnEarth(this.topPosition,0,0,this.labelOffset);
        this.UpdateFrustum();
    }

    /**
     * 调节相机杆子的高度
     * @param {number} deltaHeightMeters 
     */
    AdjustPolarHeight(deltaHeightMeters)
    {
        this.poleHeight=this.poleHeight+deltaHeightMeters;
        this.topPosition=Tools.MoveOnEarth(this.basePosition,0,0,this.poleHeight);
        this.labelPosition=Tools.MoveOnEarth(this.topPosition,0,0,this.labelOffset);
        this.UpdateFrustum();
    }

    /**
     * 调节相机水平视野角
     * @param {number} deltaHFov 
     */
    AdjustHfov(deltaHFov)
    {
        this.hfov=this.hfov+deltaHFov;
        this.UpdateFrustum();
    }

    /**
     * 
     */
    GetHtml()
    {
        const base_cartographic = Cesium.Cartographic.fromCartesian(this.basePosition);

        var result =
            `<p>ID: ${this.id}</p>
            <p>名称：${this.label}</p>
            <p>位置：${Cesium.Math.toDegrees(base_cartographic.longitude).toFixed(6)},
             ${Cesium.Math.toDegrees(base_cartographic.latitude).toFixed(6)}, 
             ${base_cartographic.height.toFixed(2)}米</p>
            <p>杠高：${this.poleHeight.toFixed(2)}米</p>
            <p>方位角：${this.viewDegree.azimuth.toFixed(1)}度</p>
            <p>俯仰角：${this.viewDegree.pitch.toFixed(1)}度</p>
            <p>水平视野角：${this.hfov.toFixed(1)}度</p>
            <p>宽高比：${this.aspect.toFixed(3)}</p>
            `;

        return result;
    }


    /**
     * 生成摄像头的标注文字
     * @returns {Cesium.Label}
     */
    GetLabel()
    {
        return {
                position: Tools.AdjustHeight(this.viewer, this.labelPosition),
                text: this.label,
                font: '50px monospace',
                fillColor: Cesium.Color.WHITE,
                outlineColor: Cesium.Color.BLACK,
                outlineWidth: 2,
                style: Cesium.LabelStyle.FILL_AND_OUTLINE,
                scaleByDistance: new Cesium.NearFarScalar(100, 1.0, 1000, 0.3) 
            };
    }

    /**
     * 获取历史视频的播放位置，实际就是视锥面
     * @returns {Cesium.Cartesian3[]}
     */
    GetReplayRange()
    {
        return this.frustum.GetFrustumCorners();
    }

    /**
     * 建立摄像机杆子
     * @param {boolean} show 
     * @returns {Cesium.GeometryInstance}
     */
    BuildPoleInstance(show)
    {
        var polar_g = new Cesium.CylinderGeometry({
            length: Cesium.Cartesian3.distance(this.basePosition, this.topPosition),
            topRadius: 0.1,
            bottomRadius: 0.2,
            vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
        });

        var center=Cesium.Cartesian3.midpoint(this.basePosition, this.topPosition, new Cesium.Cartesian3());
        var polar_i = new Cesium.GeometryInstance({
            geometry: polar_g,
            id: this.id+"_pole",
            modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(Tools.AdjustHeight(this.viewer,center)),
            attributes: {
                color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED),
                show : new Cesium.ShowGeometryInstanceAttribute(show)
            }
        });
        return polar_i;
    }

    /**
     * 建立摄像机位置的杆头
     * @param {boolean} show 
     * @returns {Cesium.GeometryInstance}
     */
    BuildTopInstance(show)
    {
        var top_i = new Cesium.GeometryInstance({
            geometry: Consts.sphereGeometry,
            id: this.id+"_top",
            modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(Tools.AdjustHeight(this.viewer,this.topPosition)),
            attributes: {
                color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.BLUE),
                show : new Cesium.ShowGeometryInstanceAttribute(show)
            }
        });

        return top_i;
    }

    /**
     * 把这个地面相机的各个Cesium.GeometryInstance放进对应Cesium.GeometryInstance数组里
     * @param {*} instances_simpleline - 这是视锥的边线
     * @param {*} instances_polygon - 这是视锥的底面的侧面
     * @param {*} instances_sphere - 这个是杆头
     * @param {*} instances_cylinder - 这个是杆子
     */
    AddToInstances(instances_simpleline, instances_polygon, instances_sphere, instances_cylinder, labelCollection, show)
    {
        this.frustum=CameraFrustum.CreateFrustumByViewDegree(this.viewer,this.topPosition,this.viewDegree,this.hfov,this.aspect,
            this.viewDegree.azimuth,2);
        instances_simpleline.push(...this.frustum.BuildSideInstances(this.id, show));
        instances_polygon.push(...this.frustum.BuildFaceInstances(this.id, show));
        instances_cylinder.push(this.BuildPoleInstance(show));
        instances_sphere.push(this.BuildTopInstance(show));
        labelCollection.add(this.GetLabel());

        // //柱子
        // var polar_g = new Cesium.PolylineGeometry({
        //     positions: [this.basePosition, this.topPosition],
        //     width: 5.0, // 折线的宽度
        //     vertexFormat: Cesium.PolylineColorAppearance.VERTEX_FORMAT,
        //     colors: [Cesium.Color.RED, Cesium.Color.RED],
        //     colorsPerVertex: true // 启用逐顶点颜色
        // });

        // // 创建几何实例
        // var polar_i = new Cesium.GeometryInstance({
        //     geometry: polar_g,
        //     id: 'polar_'+this.id
        // });

    }

    /**
     * 设定灯杆和视锥的可见性
     * @param {Cesium.Primitive} primitive_simpleline 
     * @param {Cesium.Primitive} primitive_polygon 
     * @param {Cesium.Primitive} primitive_sphere 
     * @param {Cesium.Primitive} primitive_cylinder 
     * @param {boolean} visible 
     */
    SetVisible(primitive_simpleline,primitive_polygon,
        primitive_sphere,primitive_cylinder, visible)
    {
        this.frustum.SetInstancesVisible(this.id, primitive_polygon, primitive_simpleline, visible);
        primitive_sphere.getGeometryInstanceAttributes(this.id+"_top").show = 
            Cesium.ShowGeometryInstanceAttribute.toValue(visible);
        primitive_cylinder.getGeometryInstanceAttributes(this.id+"_pole").show = 
            Cesium.ShowGeometryInstanceAttribute.toValue(visible);
    }

    /**
     * 建立用于调节的灯杆和视锥
     */
    BuildCallBackEntities()
    {
        this.UpdateFrustum();
        this.showFrustum.BuildCallBackEntities();

        this.poleEntity=this.viewer.entities.add({
            position: new Cesium.CallbackProperty((time, result)=>{
                    result=Tools.AdjustHeight(this.viewer,Cesium.Cartesian3.midpoint(this.basePosition, this.topPosition, new Cesium.Cartesian3()));    
                    return result;
                }, false), 
            cylinder: {
                length: new Cesium.CallbackProperty((time, result)=>{
                    result=this.poleHeight;    
                    return result;
                }, false), 
                topRadius: 0.1, // 圆柱体顶部半径
                bottomRadius: 0.2, // 圆柱体底部半径
                material: Cesium.Color.YELLOW, // 圆柱体的填充材质
            }
        });

        this.topEntity= this.viewer.entities.add({
            position: new Cesium.CallbackProperty((time, result)=>{
                    result=Tools.AdjustHeight(this.viewer,this.topPosition);    
                    return result;
                }, false),
            ellipsoid: {
                radii: new Cesium.Cartesian3(0.2, 0.2, 0.2), // 球的半径
                material: Cesium.Color.BLUE, // 球的材质
            }
        });

        //this.viewer.trackedEntity=this.topEntity;
    }

    /**
     * 移除用于调节的灯杆和视锥
     */
    RemoveCallBackEntities()
    {
        this.showFrustum.RemoveAllEntities();
        if (this.poleEntity!=null) this.viewer.entities.remove(this.poleEntity);
        if (this.topEntity!=null) this.viewer.entities.remove(this.topEntity);
        //this.viewer.trackedEntity = undefined;
    }

    /**
     * 输出所有参数信息
     * @returns {key-value}
     */
    Output()
    {
        const base_cartographic = Cesium.Cartographic.fromCartesian(this.basePosition);

        var result = {
            "id": this.id,
            "label": this.label,
            "lng": Cesium.Math.toDegrees(base_cartographic.longitude), 
            "lat": Cesium.Math.toDegrees(base_cartographic.latitude),
            "base": base_cartographic.height,
            "height": this.poleHeight,
            "heading": this.viewDegree.azimuth,
            "pitch": this.viewDegree.pitch,      
            "hfov": this.hfov,
            "aspect": this.aspect
        };

        return result;
    }
}

/**
 * 管理所有地面摄像头的类
 */
export class GroundCameras
{
    static SelectedOneCamera="SelectedOneCamera";    
    static ClearSelectedCamera="ClearSelectedCamera";
    /**
     * 
     * @param {Cesium.Viewer} _Viewer 
     * @param {key-value数组} _list 
     * @param {string} _name_id 
     * @param {string} _name_label 
     * @param {string} _name_lng 
     * @param {string} _name_lat 
     * @param {string} _name_base 
     * @param {string} _name_height 
     * @param {string} _name_heading 
     * @param {string} _name_pitch 
     * @param {string} _name_hfov 
     * @param {string} _name_aspect 
     */
    constructor(_Viewer, _list,   
        _name_id="id",
        _name_label="label",
        _name_lng="lng", 
        _name_lat="lat", 
        _name_base="base", 
        _name_height="height", 
        _name_heading="heading",
        _name_pitch="pitch",
        _name_hfov="hfov", 
        _name_aspect="aspect"
    )
    {
        this.viewer=_Viewer;

        this.cameras=new Map();
        for(let i=0;i<_list.length;i++)
        {
            var _data=_list[i];
            let id=_data[_name_id];
            let basePosition=Tools.GetCartesian3FromDegreesHeight(_data[_name_lng], _data[_name_lat], _data[_name_base]);
            let viewDegree=new ViewDegree(_data[_name_heading], _data[_name_pitch]);
            let oneCamera=new OneGroundCamera(_Viewer, id, _data[_name_label], basePosition, _data[_name_height], viewDegree,
                 _data[_name_hfov], _data[_name_aspect]);
            this.cameras.set(id, oneCamera);
        }

        this.appearance=new Cesium.PerInstanceColorAppearance({
                flat: true,
                //translucent: false
            });
        this.primitive_simpleline = null;
        this.primitive_polygon = null;
        this.primitive_sphere = null;
        this.primitive_cylinder = null;        

        this.labelCollection = null;

        this.changed=false;

        this.lastSelectedID="";


        this.SelectedOneCameraEvent = new CustomEvent(GroundCameras.SelectedOneCamera, {});

        this.ClearSelectedCameraEvent = new CustomEvent(GroundCameras.ClearSelectedCamera, {});

        //事件处理函数
        var handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
        handler.setInputAction((event)=> {
            var pick = this.viewer.scene.pick(event.position);
            if (Cesium.defined(pick)) {
                if (pick.id instanceof Cesium.Entity)
                {
                    //如果点击的是一个实体，这里pick.id就是Entity，有点奇怪，但事实就是这样
                }
                else
                {
                    var cid=this.GetGroundCameraID(pick.id);
                    if (cid!="")
                    {
                        if (this.lastSelectedID!="")
                        {
                            this.cameras.get(this.lastSelectedID).RemoveCallBackEntities();
                            if(this.changed) {
                                this.BuildPrimitives();
                                this.changed=false;
                            }
                            else
                            {
                                this.SetCameraVisible(this.lastSelectedID, true);
                            }
                        }
                        this.lastSelectedID=cid;
                        this.cameras.get(this.lastSelectedID).BuildCallBackEntities();
                        this.SetCameraVisible(this.lastSelectedID, false);
                        window.dispatchEvent(this.SelectedOneCameraEvent);
                    }                  
                }
            }
            else
            {
                if (this.lastSelectedID!="")
                {
                    this.cameras.get(this.lastSelectedID).RemoveCallBackEntities();
                    if(this.changed) {
                        this.BuildPrimitives();
                        this.changed=false;
                    }
                    else
                    {
                        this.SetCameraVisible(this.lastSelectedID, true);
                    }
                    this.lastSelectedID="";
                    window.dispatchEvent(this.ClearSelectedCameraEvent);
                }
            }  
        }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

        this.BuildPrimitives();

    }

    /**
     * 判断是不是点击了一个地面摄像头的组件，在primitive
     * 点击的组件有个id，其中用下划线分割，第一部分就是ID
     * @param {string} pickid 
     * @returns {string} 地面摄像头的id，如果不是,就返回 ""
     */
    GetGroundCameraID(pickid)
    {
        var vs=pickid.split("_");
        if (vs.length<2) return "";
        var id=vs[0];
        if (this.cameras.has(vs[0])) return id;   
        else return ""; 
    }

    /**
     * 获得一个摄像头
     * @returns {OneGroundCamera} 如果没有就返回null
     */
    GetCamera()
    {
        if(this.lastSelectedID!="")
        {
            return this.cameras.get(this.lastSelectedID);
        }
        else
        {
            return null;
        }
    }
    
    /**
     * 建立高效的三维实体
     */
    BuildPrimitives()
    {
        if(this.primitive_cylinder!=null) 
            this.viewer.scene.primitives.remove(this.primitive_cylinder);
        if(this.primitive_simpleline!=null) 
            this.viewer.scene.primitives.remove(this.primitive_simpleline);
        if(this.primitive_polygon!=null) 
            this.viewer.scene.primitives.remove(this.primitive_polygon);
        if(this.primitive_sphere!=null) 
            this.viewer.scene.primitives.remove(this.primitive_sphere);
        if(this.labelCollection!=null) 
            this.viewer.scene.primitives.remove(this.labelCollection);

        var _asynchronous=false;
        var instances_simpleline = [];
        var instances_polygon = [];
        var instances_cylinder = [];
        var instances_sphere = [];
        this.labelCollection = new Cesium.LabelCollection();
        for (const value of this.cameras) {
            value.AddToInstances(instances_simpleline, instances_polygon, instances_sphere, instances_cylinder, this.labelCollection, true);
        }
        
        this.primitive_simpleline = new Cesium.Primitive({
            geometryInstances: instances_simpleline,
            appearance: this.appearance,
            allowPicking: true,
            asynchronous : _asynchronous
        });

        this.primitive_polygon = new Cesium.Primitive({
            geometryInstances: instances_polygon,
            appearance: this.appearance,
            allowPicking: true,
            asynchronous : _asynchronous
        });

        this.primitive_sphere = new Cesium.Primitive({
            geometryInstances: instances_sphere,
            appearance: this.appearance,
            allowPicking: true,
            asynchronous : _asynchronous
        });

        this.primitive_cylinder = new Cesium.Primitive({
            geometryInstances: instances_cylinder,
            appearance: this.appearance,
            allowPicking: true,
            asynchronous : _asynchronous
        });

        this.viewer.scene.primitives.add(this.primitive_simpleline);
        this.viewer.scene.primitives.add(this.primitive_polygon);
        this.viewer.scene.primitives.add(this.primitive_sphere);
        this.viewer.scene.primitives.add(this.primitive_cylinder);
        this.viewer.scene.primitives.add(this.labelCollection);
    }

    /**
     * 调节方位角俯仰角
     * @param {number} deltaAzimuth - 角度
     * @param {number} deltaPitch - 角度
     */
    AdjustViewDegree(deltaAzimuth, deltaPitch)
    {
        if(this.lastSelectedID!="")
        {
            this.cameras.get(this.lastSelectedID).AdjustViewDegree(deltaAzimuth, deltaPitch);
            this.changed=true;
        }
    }

    /**
     * 调节位置
     * @param {number} deltaXmeters - 东西（米） 
     * @param {number} deltaYmeters - 南北（米） 
     * @param {number} deltaZmeters - 上下（米）
     */
    AdjustBasePosition(deltaXmeters, deltaYmeters, deltaZmeters)
    {
        if(this.lastSelectedID!="")
        {
            this.cameras.get(this.lastSelectedID).AdjustBasePosition(deltaXmeters, deltaYmeters, deltaZmeters);
            this.changed=true;
        }
    }

    /**
     * 调节杆子高度
     * @param {number} deltaHeightMeters - 杆子高度（米） 
     */
    AdjustPolarHeight(deltaHeightMeters)
    {
        if(this.lastSelectedID!="")
        {
            this.cameras.get(this.lastSelectedID).AdjustPolarHeight(deltaHeightMeters);
            this.changed=true;
        }
    }

    /**
     * 调节水平视野角
     * @param {number} deltaHfov - 角度 
     */
    AdjustHfov(deltaHfov)
    {
        if(this.lastSelectedID!="")
        {
            this.cameras.get(this.lastSelectedID).AdjustHfov(deltaHfov);
            this.changed=true;
        }
    }

    /**
     * 获取html的摄像头描述
     * @returns {string}
     */
    GetHtml()
    {
        if(this.lastSelectedID!="")
        {
            return this.cameras.get(this.lastSelectedID).GetHtml();
        }
        else
        {
            return "";
        }
    }    

    /**
     * 设备在primitive里摄像头的可视性
     * @param {number} id - 摄像头ID
     * @param {boolean} visible - 是否可见
     */
    SetCameraVisible(id, visible)
    {
        this.cameras.get(id).SetVisible(
            this.primitive_simpleline,this.primitive_polygon,
            this.primitive_sphere,this.primitive_cylinder, visible);
    }

    /**
     * 结果输出成json，并写入文件
     */
    WriteToFile()
    {
        var result=[];
        for (const value of this.cameras) {
            result.push(value.Output());
        }
        // 将对象序列化为JSON字符串
        var content = Tools.ToJson(result);
        
        Tools.DownloadToFile(content, "data.json");
    }
}

// class GroundCameras_backup
// {
//     /**
//      * 构造函数
//      * @param {Cesium.Viewer} _Viewer  
//      * @param {key-value数组} _list 
//      * @param {string} _name_id
//      * @param {string} _name_lng 
//      * @param {string} _name_lat 
//      * @param {string} _name_base 
//      * @param {string} _name_height 
//      * @param {string} _name_heading 
//      * @param {string} _name_pitch 
//      * @param {string} _name_hfov 
//      * @param {string} _name_aspect 
//      */
//     constructor(_Viewer, _list,   
//         _name_id="id",
//         _name_lng="lng", 
//         _name_lat="lat", 
//         _name_base="base", 
//         _name_height="height", 
//         _name_heading="heading",
//         _name_pitch="pitch",
//         _name_hfov="hfov", 
//         _name_aspect="aspect"
//     )
//     {
//         this.viewer=_Viewer;

//         this.cameras=new Map();
//         for(let i=0;i<_list.length;i++)
//         {
//             var _data=_list[i];
//             let id=_data[_name_id];
//             let basePosition=Tools.GetCartesian3FromDegreesHeight(_data[_name_lng], _data[_name_lat], _data[_name_base]);
//             let viewDegree=new ViewDegree(_data[_name_heading], _data[_name_pitch]);
//             let oneCamera=new OneGroundCamera(_Viewer, id, basePosition, _data[_name_height], viewDegree,
//                  _data[_name_hfov], _data[_name_aspect]);
//             this.cameras.set(id, oneCamera);
//         }

//         this.appearance=new Cesium.PerInstanceColorAppearance({
//                 flat: true,
//                 //translucent: false
//             });
//         this.primitive_simpleline = null;
//         this.primitive_polygon = null;
//         this.primitive_sphere = null;
//         this.primitive_cylinder = null;        



//         this.labelCollection = null;

//         this.videoElement = document.createElement('video');
//         //this.videoElement.loop = true;
//         this.videoElement.src="http://localhost:5123/stream";
//         //this.videoElement.muted = true;

//         // this.image=document.createElement('image');
//         // this.image.src="http://localhost:5123/stream";

//         this.lastPlayID="";
        

//         this.frustumScreen= _Viewer.entities.add({
//             id: 'frustumScreen',
//             show: false,
//             polygon: {
//                 hierarchy: Cesium.Cartesian3.fromDegreesArray([
//                     0, 0,
//                     1, 0,
//                     1, 1,
//                     0, 1
//                 ]),
//                 material: this.videoElement, // 使用视频元素作为材质
//                 clampToGround:false,
//                 perPositionHeight: true
//             }
//         });

//         var handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
//         handler.setInputAction((event)=> {
//             var pick = this.viewer.scene.pick(event.position);
//             if (Cesium.defined(pick)) {
//                 if (pick.id instanceof Cesium.Entity)
//                 {
//                     this.RestoreCamera(pick.id);
//                 }
//                 else
//                 {
//                     this.play(pick.id, false);
//                 }
//             }
//         }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

//         handler.setInputAction((event)=> {
//             var pick = this.viewer.scene.pick(event.position);
//             if (Cesium.defined(pick)) {
//                 {
//                     this.play(pick.id, true);
//                 }
//             }
//         }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
//         this.BuildPrimitives();

//         this.videoManager=new VideoManager("172.17.237.2","wgh","WGH_54343110");
//         this.videoManager.Login();
//         setTimeout(()=>{
//                 this.videoManager.BuildChannel();
//             }, 1000);   
//     }

//     GetRTSP(id)
//     {
//         return this.videoManager.GetRTSP(id);
//     }
    
//     BuildPrimitives(needHideID="")
//     {
//         if(this.primitive_cylinder!=null) 
//             this.viewer.scene.primitives.remove(this.primitive_cylinder);
//         if(this.primitive_simpleline!=null) 
//             this.viewer.scene.primitives.remove(this.primitive_simpleline);
//         if(this.primitive_polygon!=null) 
//             this.viewer.scene.primitives.remove(this.primitive_polygon);
//         if(this.primitive_sphere!=null) 
//             this.viewer.scene.primitives.remove(this.primitive_sphere);

//         var _asynchronous=false;
//         var instances_simpleline = [];
//         var instances_polygon = [];
//         var instances_cylinder = [];
//         var instances_sphere = [];
//         this.labelCollection = this.viewer.scene.primitives.add(new Cesium.LabelCollection());
//         for (const [key, value] of this.cameras) {
//             value.AddToInstances(instances_simpleline, instances_polygon, instances_sphere, instances_cylinder, key!=needHideID);
//             this.labelCollection.add(value.GetLabel());
//         }
        
//         this.primitive_simpleline = new Cesium.Primitive({
//             geometryInstances: instances_simpleline,
//             appearance: this.appearance,
//             allowPicking: true,
//             asynchronous : _asynchronous
//         });

//         this.primitive_polygon = new Cesium.Primitive({
//             geometryInstances: instances_polygon,
//             appearance: this.appearance,
//             allowPicking: true,
//             asynchronous : _asynchronous
//         });

//         this.primitive_sphere = new Cesium.Primitive({
//             geometryInstances: instances_sphere,
//             appearance: this.appearance,
//             allowPicking: true,
//             asynchronous : _asynchronous
//         });

//         this.primitive_cylinder = new Cesium.Primitive({
//             geometryInstances: instances_cylinder,
//             appearance: this.appearance,
//             allowPicking: true,
//             asynchronous : _asynchronous
//         });

//         this.viewer.scene.primitives.add(this.primitive_simpleline);
//         this.viewer.scene.primitives.add(this.primitive_polygon);
//         this.viewer.scene.primitives.add(this.primitive_sphere);
//         this.viewer.scene.primitives.add(this.primitive_cylinder);
//     }



//     AdjustViewDegree(deltaAzimuth, deltaPitch)
//     {
//         if(this.lastPlayID!="")
//         {
//             this.cameras.get(this.lastPlayID).AdjustViewDegree(deltaAzimuth, deltaPitch);
//             this.BuildPrimitives(this.lastPlayID);
//             setTimeout(()=>{
//                 this.play(this.lastPlayID, true);
//             }, 100);  //因为primitive还没ready，所以要等一下
//         }
//     }

//     AdjustBasePosition(deltaXmeters, deltaYmeters, deltaZmeters)
//     {
//         if(this.lastPlayID!="")
//         {
//             this.cameras.get(this.lastPlayID).AdjustBasePosition(deltaXmeters, deltaYmeters, deltaZmeters);
//             this.BuildPrimitives(this.lastPlayID);
//             setTimeout(()=>{
//                 this.play(this.lastPlayID, true);
//             }, 100);  //因为primitive还没ready，所以要等一下
//         }
//     }

//     AdjustPolarHeight(deltaHeightMeters)
//     {
//         if(this.lastPlayID!="")
//         {
//             this.cameras.get(this.lastPlayID).AdjustPolarHeight(deltaHeightMeters);
//             this.BuildPrimitives(this.lastPlayID);
//             setTimeout(()=>{
//                 this.play(this.lastPlayID, true);
//             }, 100);  //因为primitive还没ready，所以要等一下
//         }
//     }

//     AdjustHfov(deltaHfov)
//     {
//         if(this.lastPlayID!="")
//         {
//             this.cameras.get(this.lastPlayID).AdjustHfov(deltaHfov);
//             this.BuildPrimitives(this.lastPlayID);
//             setTimeout(()=>{
//                 this.play(this.lastPlayID, true);
//             }, 100);  //因为primitive还没ready，所以要等一下
//         }
//     }

//     RotateMaterial()
//     {
//         if(this.lastPlayID!="")
//         {
//             this.cameras.get(this.lastPlayID).RotateMaterial();
//             this.play(this.lastPlayID, true);
//         }
//     }

//     RestoreCamera(entity)
//     {
//         if (entity==this.frustumScreen)
//         {
//             this.videoElement.pause();
//             this.SetCameraVisible(this.lastPlayID, true);
//             this.frustumScreen.show=false;
//         }
//     }

//     play(pickid, needFocus, needHide=true)
//     {
//         var vs=pickid.split("_");
//         if (vs.length<2) return;
//         var id=vs[0];
//         if (!this.cameras.has(id)) return;        

//         // if (this.videoManager.playingChannelCode!="")
//         // {
//         //     this.videoManager.StopRTSP(this.videoManager.playingChannelCode);
//         // }
//         if (this.lastPlayID!="") 
//         {
//             if (id!=this.lastPlayID)             
//             {    
//                 this.SetCameraVisible(this.lastPlayID, true);
//             }
//         }
//         this.lastPlayID=id;
//         this.SetCameraVisible(this.lastPlayID, !needHide);


//         // this.videoElement.pause();

//         let oneCamera=this.cameras.get(id);

//         this.frustumScreen.polygon.hierarchy=oneCamera.GetReplayRange();
//         this.frustumScreen.polygon.stRotation = Cesium.Math.toRadians(oneCamera.rotateMaterial); 

//         // this.videoElement.src=rtsp;//oneCamera.video;
//         // this.videoElement.play();
//         this.frustumScreen.show=true;

//         if (needFocus)
//         {
//             this.viewer.scene.camera.frustumDebug = false;
//             this.viewer.scene.camera.setView({
//                 destination :Tools.AdjustHeight(this.viewer, 
//                     Tools.GetPositionAlongViewDegree(oneCamera.topPosition, oneCamera.viewDegree, -2)),
//                 orientation : {
//                     heading : Cesium.Math.toRadians(oneCamera.viewDegree.azimuth),
//                     pitch : Cesium.Math.toRadians(oneCamera.viewDegree.pitch),
//                     roll : 0.0
//                 },
//             });
//             this.viewer.scene.camera.frustumDebug = false;
//         }

//         var jsonString = JSON.stringify(oneCamera.Output(), null, 4); 
//         console.log(jsonString);
//     }

//     SetCameraVisible(id, visible)
//     {
//         this.cameras.get(id).SetVisible(
//             this.primitive_simpleline,this.primitive_polygon,
//             this.primitive_sphere,this.primitive_cylinder, visible);
//     }

//     Output()
//     {
//         var result=[];
//         for (const [key, value] of this.cameras) {
//             result.push(value.Output());
//         }

//         // 将对象序列化为JSON字符串
//         var content = JSON.stringify(result, null, 4); // 使用4个空格进行缩进，使输出更易读

//         // 创建一个Blob对象，包含文件内容
//         var blob = new Blob([content], { type: 'text/plain' });

//         // 创建一个URL对象，指向Blob对象
//         var url = URL.createObjectURL(blob);

//         // 创建一个<a>元素
//         var link = document.createElement('a');

//         // 设置href属性为Blob对象的URL
//         link.href = url;

//         // 设置download属性，指定下载时的文件名
//         link.download = "data.json"; // 使用用户输入的文件名

//         // 触发点击事件
//         document.body.appendChild(link);
//         link.click();

//         // 移除临时创建的<a>元素
//         document.body.removeChild(link);

//         // 释放Blob对象的URL
//         URL.revokeObjectURL(url);
//     }
// }

/**
 * 宇视相机连接类
 */
export class VideoManager
{
          
    static ReturnRtsp="ReturnRtsp";

    /**
     * 
     * @param {string} serIP 
     * @param {string} userName 
     * @param {string} passWord 
     * @param {number} channelCount 
     */
    constructor(serIP="172.17.237.2",userName="wgh", passWord="WGH_54343110", channelCount=100 )
    {
        this.url="http://" +serIP+":8088";        
        this.playingChannelCodes=[];  
        this.access_token="";
        this.IntervalID=-1;
        this.Login(userName, passWord, channelCount);
    }

    /**
     * 内部函数，心跳函数
     */
    KeepAlive()
    {
        $.ajax(
            {
                'type': 'POST',
                'url': this.url + '/VIID/hadesadapter/user/keepalive',
                'headers': {
                    'Authorization': this.access_token
                },
                'contentType':'application/json',
                'success':(res)=> {
                        if(res.ErrCode === 0){
                            console.log('保活成功');
                        }
                        else{
                            console.log('保活失败'+this.access_token);  
                        }
                    },                                          
                'error': (err)=> {
                    console.log(err)
                }  
            }
        ); 
    }

    /**
     * 内部函数，一次登录，获得access_code，然后二次登录，获得token
     * @param {string} userName 
     * @param {string} passWord 
     */
    Login(userName, passWord, channelCount)
    {
        jQuery.support.cors = true;        
        $.ajax(
        {
            type:'POST',
            url:this.url+"/VIID/login/v2",
            success:
                (returnedData)=>
                    {                        
                        if(returnedData.AccessCode != null){
                            var access_code = returnedData.AccessCode;
                            console.log(access_code);
                            this.LoginSecond(userName, passWord, access_code, channelCount);
                        }else{
                            alert("access_code is null!"); 
                            this.access_token="";
                        }
                    },
            error:(xhr,status,error)=>
            {
                console.log(xhr,status,error);
            }
        });
    }    

    /**
     * 内部函数，二次登录，获得token，开题直播通道
     * @param {string} userName 
     * @param {string} passWord 
     * @param {string} access_code 
     * @param {number} channelCount 
     */
    LoginSecond(userName, passWord, access_code, channelCount)
    {
        var enuserName = base64encode(userName);
        var enuserPwd = hex_md5(passWord);
        var login_signature_nomd5 = enuserName + access_code + enuserPwd;
        var login_signature = hex_md5(login_signature_nomd5);
        var data='{"UserName":"' + userName + '","AccessCode":"' + access_code+'","LoginSignature":"'+login_signature+'"}';
      	
        jQuery.support.cors = true;
        $.ajax(
        {
            type:"POST",
            url:this.url+"/VIID/login/v2",
            data:data,
            contentType:"application/json",
            success:
                (returnedData)=>
                    {
                        if(returnedData.AccessToken != null){
                            console.log(returnedData.AccessToken);
                            this.RegisterChannels(channelCount, returnedData.AccessToken);
                        }else{
                            alert("access_token is null!");   
                            this.access_token="";
                        }
                    }
        }
        ); 
    }

    /**
     * 内部函数：开通视频通道，启动心跳协议
     * @param {number} channelCount - 通道数量 
     */
    RegisterChannels(channelCount, access_token)
    {
        var data = JSON.stringify({
            Num: channelCount 
        })
        jQuery.support.cors = true;    
        $.ajax({
            'type': 'POST',
            'url': this.url + '/VIID/live/player/build',
            'data': data,
            'headers': {
                'Authorization': access_token
            },
            'contentType':'application/json',
            'success': (res)=> {
                if(res.ErrCode === 0){ 
                    console.log('注册播放器成功');
                    this.access_token = access_token;
                    this.IntervalID=setInterval(()=>{this.KeepAlive();}, 30000);

                } else {
                    console.log('注册播放器失败');
                    this.access_token="";
                    this.LogOut();
                }                
            },
            'error': (err)=> {
                console.log('注册播放器失败:'+err);
            }
        })

    }

    /**
     * 通过window.dispatchEvent(ReturnRtspEvent);返回label及rtsp     * 
     * @param {string} cameraLabel 
     * @param {string} cameraCode 
     */
    GetRTSP(cameraLabel, cameraCode)
    {
        if (this.playingChannelCodes.length>=this.channelCount)
        {
            this.StopRTSP(this.playingChannelCodes[0]);
            this.playingChannelCodes.shift();
        }
        var data =  JSON.stringify({
            CameraCode: cameraCode,
            StreamType: 1
        });

        $.ajax({
            'type': 'POST',
            'url': this.url + '/VIID/live/start',
            'data': data,
            'headers': {
                'Authorization': this.access_token
                },
            'contentType':'application/json',
            'success': (res)=> {
                if(res.ErrCode !=0){
                    alert('获取RTSP URL失败，错误码：' + res.ErrCode + '\r\n' + '错误信息为：' + res.ErrMsg)
                    return;
                } else {      
                    console.log(res.Result.RtspURL);
                    this.playingChannelCodes.push(res.Result.ChannelCode);
                    var ReturnRtspEvent=new CustomEvent(this.ReturnRtsp, {
                        label: cameraLabel,
                        rtsp: res.Result.RtspURL
                    });
                    window.dispatchEvent(ReturnRtspEvent);
                }
            }
        });
    }

    /**
     * 内部函数，关闭一个视频流
     * @param {string} channelCode 
     */
    StopRTSP(channelCode)
    {
        var data =  JSON.stringify({
            ChannelCode: channelCode
        });

        $.ajax({
            'type': 'POST',
            'url': this.url + '/VIID/live/stop',
            'data': data,
            'headers': {
                'Authorization': this.access_token
                },
            'contentType':'application/json',
            'success': (res)=> {
                if(res.ErrCode !=0){
                    alert('停止RTSP失败，错误码：' + res.ErrCode + '\r\n' + '错误信息为：' + res.ErrMsg);
                } 
            }
        });
    }

    /**
     * 停止心跳一分半后，自动下线
     */
    LogOut()
    {
        if (this.IntervalID!=-1)
            clearInterval(this.IntervalID);
    }
}